diff --git a/.travis.yml b/.travis.yml index 076b53675..63706e849 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,8 +8,6 @@ cache: addons: apt: packages: - - chromium-chromedriver - - google-chrome-stable # Postgresql version used here should match production - postgresql-9.4 - postgresql-client-9.4 @@ -18,7 +16,7 @@ addons: secure: "PfhLGBKRgNqhKuYCJsK+VPhdAzcgWFGeeOyxC/eS8gtlvIISVdgyZE+r30uIei0DFI6zEiN62eW4d+xtT4j7/e2ZcAcx7U52mza/SnQNuu3nCGQDJB8VOvV5NbnwXfi8vfr4e889Mt7k3ocd2c4gqB4UtRqrzhygj7HN+B/GfEk=" env: matrix: - - GROWSTUFF_ELASTICSEARCH=true RSPEC_TAG=elasticsearch COVERAGE=true + - GROWSTUFF_ELASTICSEARCH=true RSPEC_TAG=elasticsearch COVERAGE=true ELASTIC_SEARCH_VERSION="6.2.3" - GROWSTUFF_ELASTICSEARCH=false RSPEC_TAG=~elasticsearch COVERAGE=false - STATIC_CHECKS=true PERCY_CHECKS=true global: @@ -27,9 +25,13 @@ env: - GROWSTUFF_SITE_NAME="Growstuff (travis)" - RAILS_SECRET_TOKEN='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' before_install: + - sudo apt clean + # - sudo apt update + - sudo apt install dpkg + - sudo apt install google-chrome-stable - ./script/install_codeclimate.sh - ./script/install_linters.sh - - ELASTIC_SEARCH_VERSION="6.2.3" ./script/install_elasticsearch.sh + - ./script/install_elasticsearch.sh before_script: - > if [ "${COVERAGE}" = "true" ]; then diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 01aeaaba7..7c268fda4 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -88,6 +88,8 @@ submit the change with your pull request. - Ahmed Shahin / [codeminator](https://www.github.com/codeminator) - Brandon Baker / [brandonbaker40](https://github.com/brandonbaker40) - Alex Darr / [apdarr](https://github.com/apdarr) +- Taylor William / [bestest-mensch](https://github.com/bestest-mensch) +- André Aubin / [lambda2](https://github.com/lambda2) ## Bots diff --git a/Gemfile b/Gemfile index cfa8ef248..2672e0aaf 100644 --- a/Gemfile +++ b/Gemfile @@ -54,9 +54,6 @@ gem 'unicorn' # http server gem "comfortable_mexican_sofa", "~> 2.0.0" -gem 'bootstrap-kaminari-views' # bootstrap views for kaminari -gem 'kaminari' # pagination - gem 'active_utils' gem 'sidekiq' @@ -73,6 +70,9 @@ gem 'devise' # nicely formatted URLs gem 'friendly_id' +# validates URLs +gem "validate_url" + # gravatars gem 'gravatar-ultimate' @@ -118,6 +118,9 @@ gem 'rack-protection', '>= 2.0.1' # Member to member messaging system gem 'mailboxer' +gem 'faraday' +gem 'faraday_middleware' + group :production do gem 'bonsai-elasticsearch-rails' # Integration with Bonsa-Elasticsearch on heroku gem 'dalli' diff --git a/Gemfile.lock b/Gemfile.lock index 035b15fe6..f09e3f1f2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -57,8 +57,8 @@ GEM adamantium (0.2.0) ice_nine (~> 0.11.0) memoizable (~> 0.4.0) - addressable (2.6.0) - public_suffix (>= 2.0.2, < 4.0) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) anima (0.3.1) abstract_type (~> 0.0.7) adamantium (~> 0.2) @@ -82,10 +82,7 @@ GEM sassc-rails (>= 2.0.0) bootstrap-datepicker-rails (1.8.0.1) railties (>= 3.0) - bootstrap-kaminari-views (0.0.5) - kaminari (>= 0.13) - rails (>= 3.1) - bootstrap_form (4.2.0) + bootstrap_form (4.3.0) actionpack (>= 5.0) activemodel (>= 5.0) builder (3.2.3) @@ -94,7 +91,7 @@ GEM uniform_notifier (~> 1.11) byebug (11.0.1) cancancan (3.0.1) - capybara (3.28.0) + capybara (3.29.0) addressable mini_mime (>= 0.1.3) nokogiri (~> 1.8) @@ -112,9 +109,8 @@ GEM activemodel (>= 4.0.0) activesupport (>= 4.0.0) mime-types (>= 1.16) - chartkick (3.2.1) - childprocess (1.0.1) - rake (< 13.0) + chartkick (3.3.0) + childprocess (3.0.0) codeclimate-test-reporter (1.0.9) simplecov (<= 0.13) coderay (1.1.2) @@ -149,12 +145,12 @@ GEM term-ansicolor (~> 1.3) thor (~> 0.19.1) tins (~> 1.6) - crass (1.0.4) - csv_shaper (1.3.0) + crass (1.0.5) + csv_shaper (1.3.1) activesupport (>= 3.0.0) dalli (2.7.10) database_cleaner (1.7.0) - devise (4.7.0) + devise (4.7.1) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) @@ -169,7 +165,7 @@ GEM elasticsearch-transport (= 6.8.0) elasticsearch-api (6.8.0) multi_json - elasticsearch-model (6.0.0) + elasticsearch-model (7.0.0) activesupport (> 3) elasticsearch (> 1) hashie @@ -178,26 +174,28 @@ GEM faraday multi_json equalizer (0.0.11) - erubi (1.8.0) + erubi (1.9.0) erubis (2.7.0) excon (0.64.0) execjs (2.7.0) - factory_bot (5.0.2) + factory_bot (5.1.1) activesupport (>= 4.2.0) - factory_bot_rails (5.0.2) - factory_bot (~> 5.0.2) + factory_bot_rails (5.1.1) + factory_bot (~> 5.1.0) railties (>= 4.2.0) - faker (2.2.0) - i18n (>= 0.8) - faraday (0.15.4) + faker (2.7.0) + i18n (>= 1.6, < 1.8) + faraday (0.17.0) multipart-post (>= 1.2, < 3) + faraday_middleware (0.13.1) + faraday (>= 0.7.4, < 1.0) ffi (1.11.1) figaro (1.1.1) thor (~> 0.14) flickraw (0.9.10) - font-awesome-sass (5.9.0) + font-awesome-sass (5.11.2) sassc (>= 1.11) - friendly_id (5.2.5) + friendly_id (5.3.0) activerecord (>= 4.0.0) geocoder (1.4.9) gibbon (1.2.1) @@ -223,10 +221,9 @@ GEM haml (>= 4.0.6, < 6.0) html2haml (>= 1.0.1) railties (>= 5.1) - haml_lint (0.33.0) + haml_lint (0.34.0) haml (>= 4.0, < 5.2) rainbow - rake (>= 10, < 13) rubocop (>= 0.50.0) sysexits (~> 1.1) hashie (3.6.0) @@ -244,7 +241,7 @@ GEM httparty (0.17.0) mime-types (~> 3.0) multi_xml (>= 0.5.2) - i18n (1.6.0) + i18n (1.7.0) concurrent-ruby (~> 1.0) i18n-tasks (0.9.29) activesupport (>= 4.0.2) @@ -273,18 +270,6 @@ GEM concurrent-ruby railties (>= 4.1) jwt (2.2.1) - kaminari (1.1.1) - activesupport (>= 4.1.0) - kaminari-actionview (= 1.1.1) - kaminari-activerecord (= 1.1.1) - kaminari-core (= 1.1.1) - kaminari-actionview (1.1.1) - actionview - kaminari-core (= 1.1.1) - kaminari-activerecord (1.1.1) - activerecord - kaminari-core (= 1.1.1) - kaminari-core (1.1.1) kgio (2.11.2) kramdown (2.1.0) launchy (2.4.3) @@ -293,11 +278,10 @@ GEM rails (>= 4.2.0) letter_opener (1.7.0) launchy (~> 2.2) - listen (3.1.5) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - ruby_dep (~> 1.2) - loofah (2.2.3) + listen (3.2.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + loofah (2.3.1) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) @@ -323,23 +307,23 @@ GEM mini_magick (4.9.4) mini_mime (1.0.2) mini_portile2 (2.4.0) - minitest (5.11.3) + minitest (5.12.2) moneta (1.0.0) multi_json (1.11.3) multi_xml (0.6.0) multipart-post (2.1.1) - newrelic_rpm (6.5.0.357) - nio4r (2.4.0) + newrelic_rpm (6.7.0.359) + nio4r (2.5.2) nokogiri (1.10.4) mini_portile2 (~> 2.4.0) oauth (0.5.4) - oauth2 (1.4.1) - faraday (>= 0.8, < 0.16.0) + oauth2 (1.4.2) + faraday (>= 0.8, < 2.0) jwt (>= 1.0, < 3.0) multi_json (~> 1.3) multi_xml (~> 0.5) rack (>= 1.2, < 3) - oj (3.9.0) + oj (3.9.2) omniauth (1.9.0) hashie (>= 3.4.6, < 3.7.0) rack (>= 1.6.2, < 3) @@ -358,8 +342,8 @@ GEM omniauth-oauth (~> 1.1) rack orm_adapter (0.5.0) - parallel (1.17.0) - parser (2.6.3.0) + parallel (1.18.0) + parser (2.6.5.0) ast (~> 2.4.0) percy-capybara (4.0.2) pg (0.21.0) @@ -368,8 +352,8 @@ GEM moneta (~> 1.0.0) popper_js (1.14.5) procto (0.0.3) - public_suffix (3.1.1) - puma (4.1.0) + public_suffix (4.0.1) + puma (4.3.0) nio4r (~> 2.0) rack (2.0.7) rack-protection (2.0.7) @@ -399,8 +383,8 @@ GEM rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.2.0) - loofah (~> 2.2, >= 2.2.2) + rails-html-sanitizer (1.3.0) + loofah (~> 2.3) rails-i18n (5.1.3) i18n (>= 0.7, < 2) railties (>= 5.0, < 6) @@ -417,64 +401,63 @@ GEM thor (>= 0.19.0, < 2.0) rainbow (3.0.0) raindrops (0.19.0) - rake (12.3.3) + rake (13.0.0) rb-fsevent (0.10.3) rb-inotify (0.10.0) ffi (~> 1.0) - redis (4.1.2) + redis (4.1.3) regexp_parser (1.6.0) responders (3.0.0) actionpack (>= 5.0) railties (>= 5.0) - rspec (3.8.0) - rspec-core (~> 3.8.0) - rspec-expectations (~> 3.8.0) - rspec-mocks (~> 3.8.0) + rspec (3.9.0) + rspec-core (~> 3.9.0) + rspec-expectations (~> 3.9.0) + rspec-mocks (~> 3.9.0) rspec-activemodel-mocks (1.1.0) activemodel (>= 3.0) activesupport (>= 3.0) rspec-mocks (>= 2.99, < 4.0) - rspec-core (3.8.1) - rspec-support (~> 3.8.0) - rspec-expectations (3.8.4) + rspec-core (3.9.0) + rspec-support (~> 3.9.0) + rspec-expectations (3.9.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-mocks (3.8.1) + rspec-support (~> 3.9.0) + rspec-mocks (3.9.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-rails (3.8.2) + rspec-support (~> 3.9.0) + rspec-rails (3.9.0) actionpack (>= 3.0) activesupport (>= 3.0) railties (>= 3.0) - rspec-core (~> 3.8.0) - rspec-expectations (~> 3.8.0) - rspec-mocks (~> 3.8.0) - rspec-support (~> 3.8.0) - rspec-support (3.8.2) + rspec-core (~> 3.9.0) + rspec-expectations (~> 3.9.0) + rspec-mocks (~> 3.9.0) + rspec-support (~> 3.9.0) + rspec-support (3.9.0) rspectre (0.0.1) anima (~> 0.3) concord (~> 0.1) parser (~> 2.3) rspec (~> 3.0) unparser (~> 0.2) - rubocop (0.74.0) + rubocop (0.76.0) jaro_winkler (~> 1.5.1) parallel (~> 1.10) parser (>= 2.6) rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 1.7) - rubocop-rails (2.3.1) + rubocop-rails (2.3.2) rack (>= 1.1) rubocop (>= 0.72.0) - rubocop-rspec (1.35.0) - rubocop (>= 0.60.0) + rubocop-rspec (1.36.0) + rubocop (>= 0.68.1) ruby-progressbar (1.10.1) ruby-units (2.3.1) - ruby_dep (1.5.0) ruby_parser (3.13.1) sexp_processor (~> 4.9) - rubyzip (1.2.3) + rubyzip (2.0.0) sass (3.7.4) sass-listen (~> 4.0.0) sass-listen (4.0.0) @@ -486,29 +469,29 @@ GEM sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) - sassc (2.0.1) + sassc (2.2.1) ffi (~> 1.9) - rake sassc-rails (2.1.2) railties (>= 4.0.0) sassc (>= 2.0) sprockets (> 3.0) sprockets-rails tilt - scout_apm (2.5.2) + scout_apm (2.6.3) + parser searchkick (4.1.0) activemodel (>= 5) elasticsearch (>= 6) hashie - selenium-webdriver (3.142.3) - childprocess (>= 0.5, < 2.0) - rubyzip (~> 1.2, >= 1.2.2) + selenium-webdriver (3.142.6) + childprocess (>= 0.5, < 4.0) + rubyzip (>= 1.2.2) sexp_processor (4.12.1) - sidekiq (5.2.7) - connection_pool (~> 2.2, >= 2.2.2) - rack (>= 1.5.0) - rack-protection (>= 1.5.0) - redis (>= 3.3.5, < 5) + sidekiq (6.0.3) + connection_pool (>= 2.2.2) + rack (>= 2.0.0) + rack-protection (>= 2.0.0) + redis (>= 4.1.0) simplecov (0.12.0) docile (~> 1.1.0) json (>= 1.8, < 3) @@ -522,20 +505,20 @@ GEM activesupport (>= 4.0) sprockets (>= 3.0.0) sysexits (1.2.0) - temple (0.8.1) + temple (0.8.2) term-ansicolor (1.7.1) tins (~> 1.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) thor (0.19.4) thread_safe (0.3.6) - tilt (2.0.9) + tilt (2.0.10) timecop (0.9.1) tins (1.21.0) trollop (1.16.2) tzinfo (1.2.5) thread_safe (~> 0.1) - uglifier (4.1.20) + uglifier (4.2.0) execjs (>= 0.3.0, < 3) unicode-display_width (1.6.0) unicorn (5.5.1) @@ -550,11 +533,14 @@ GEM equalizer (~> 0.0.9) parser (~> 2.6.3) procto (~> 0.0.2) + validate_url (1.0.8) + activemodel (>= 3.0.0) + public_suffix warden (1.2.8) rack (>= 2.0.6) - webdrivers (4.1.2) + webdrivers (4.1.3) nokogiri (~> 1.6) - rubyzip (~> 1.0) + rubyzip (>= 1.3.0) selenium-webdriver (>= 3.0, < 4.0) webrat (0.7.3) nokogiri (>= 1.2.0) @@ -563,7 +549,7 @@ GEM websocket-driver (0.7.1) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.4) - will_paginate (3.1.8) + will_paginate (3.2.1) will_paginate-bootstrap4 (0.2.2) will_paginate (~> 3.0, >= 3.0.0) xmlrpc (0.3.0) @@ -582,7 +568,6 @@ DEPENDENCIES bonsai-elasticsearch-rails bootstrap (>= 4.3.1) bootstrap-datepicker-rails - bootstrap-kaminari-views bootstrap_form (>= 4.2.0) bullet bundler (>= 1.1.5) @@ -604,6 +589,8 @@ DEPENDENCIES elasticsearch (< 7.0.0) factory_bot_rails faker + faraday + faraday_middleware figaro flickraw font-awesome-sass @@ -621,7 +608,6 @@ DEPENDENCIES jquery-ui-rails js-routes jsonapi-resources - kaminari leaflet-rails letter_opener listen @@ -662,6 +648,7 @@ DEPENDENCIES timecop uglifier unicorn + validate_url webdrivers webrat will_paginate diff --git a/Procfile b/Procfile index 9c8237414..528ca4bfb 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb +web: bundle exec puma -C config/puma.rb \ No newline at end of file diff --git a/README.md b/README.md index 46dbdcb09..23f099983 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Growstuff +# 🌱 Growstuff [](https://travis-ci.org/Growstuff/growstuff) [](https://coveralls.io/github/Growstuff/growstuff?branch=dev) @@ -8,10 +8,9 @@ Welcome to the Growstuff project. You can find our app at https://www.growstuff.org -Growstuff is an open source/open data project to create a website for -food gardeners. We crowdsource information on what our members are -growing and harvesting, aggregate it, and make it available as open data -via our API. +Growstuff is an open source/open data project for food gardeners. We +crowdsource information on what our members are growing and harvesting, +aggregate it, and make it available as open data via our API. Growstuff was founded in 2012 and has been built by dozens of [contributors](CONTRIBUTORS.md). We are an inclusive, welcoming project, and diff --git a/app/assets/images/icons/COPYRIGHT b/app/assets/images/icons/COPYRIGHT new file mode 100644 index 000000000..acf08b86d --- /dev/null +++ b/app/assets/images/icons/COPYRIGHT @@ -0,0 +1 @@ +Icons in this folder attributable to Icons8.com. Used with permission. diff --git a/app/assets/images/icons/add-photo.svg b/app/assets/images/icons/add-photo.svg new file mode 100644 index 000000000..a3a042c10 --- /dev/null +++ b/app/assets/images/icons/add-photo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/ant.svg b/app/assets/images/icons/ant.svg new file mode 100644 index 000000000..da94eac67 --- /dev/null +++ b/app/assets/images/icons/ant.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/bee.svg b/app/assets/images/icons/bee.svg new file mode 100644 index 000000000..fa3cdfc99 --- /dev/null +++ b/app/assets/images/icons/bee.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/bug.svg b/app/assets/images/icons/bug.svg new file mode 100644 index 000000000..bc5b5da5f --- /dev/null +++ b/app/assets/images/icons/bug.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/butterfly.svg b/app/assets/images/icons/butterfly.svg new file mode 100644 index 000000000..551cc7323 --- /dev/null +++ b/app/assets/images/icons/butterfly.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/cat.svg b/app/assets/images/icons/cat.svg new file mode 100644 index 000000000..14fe24a4f --- /dev/null +++ b/app/assets/images/icons/cat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/delete.svg b/app/assets/images/icons/delete.svg index 899b79b86..458ebd746 100644 --- a/app/assets/images/icons/delete.svg +++ b/app/assets/images/icons/delete.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/app/assets/images/icons/earth-worm.svg b/app/assets/images/icons/earth-worm.svg new file mode 100644 index 000000000..e5e9b1f13 --- /dev/null +++ b/app/assets/images/icons/earth-worm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/edit.svg b/app/assets/images/icons/edit.svg new file mode 100644 index 000000000..de8a6230b --- /dev/null +++ b/app/assets/images/icons/edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/finish.svg b/app/assets/images/icons/finish.svg new file mode 100644 index 000000000..37ff43b0e --- /dev/null +++ b/app/assets/images/icons/finish.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/garden.svg b/app/assets/images/icons/garden.svg deleted file mode 100644 index 11b9396f7..000000000 --- a/app/assets/images/icons/garden.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/assets/images/icons/gardener.svg b/app/assets/images/icons/gardener.svg new file mode 100644 index 000000000..3f47a2fcd --- /dev/null +++ b/app/assets/images/icons/gardener.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/gardens.svg b/app/assets/images/icons/gardens.svg new file mode 100644 index 000000000..fa71ba208 --- /dev/null +++ b/app/assets/images/icons/gardens.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/grass.svg b/app/assets/images/icons/grass.svg new file mode 100644 index 000000000..9d77d945d --- /dev/null +++ b/app/assets/images/icons/grass.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/growing.svg b/app/assets/images/icons/growing.svg new file mode 100644 index 000000000..bfebc1bb5 --- /dev/null +++ b/app/assets/images/icons/growing.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/harvest-add.svg b/app/assets/images/icons/harvest-add.svg new file mode 100644 index 000000000..f40bc92bc --- /dev/null +++ b/app/assets/images/icons/harvest-add.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/app/assets/images/icons/harvest.svg b/app/assets/images/icons/harvest.svg index e941e9e92..8b536cc22 100644 --- a/app/assets/images/icons/harvest.svg +++ b/app/assets/images/icons/harvest.svg @@ -1,5 +1 @@ - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/assets/images/icons/home.svg b/app/assets/images/icons/home.svg deleted file mode 100644 index ae5dc8d22..000000000 --- a/app/assets/images/icons/home.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/assets/images/icons/hose.svg b/app/assets/images/icons/hose.svg new file mode 100644 index 000000000..448d1cd78 --- /dev/null +++ b/app/assets/images/icons/hose.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/insect.svg b/app/assets/images/icons/insect.svg new file mode 100644 index 000000000..635b3259b --- /dev/null +++ b/app/assets/images/icons/insect.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/ladybird.svg b/app/assets/images/icons/ladybird.svg new file mode 100644 index 000000000..a580ccae1 --- /dev/null +++ b/app/assets/images/icons/ladybird.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/notification.svg b/app/assets/images/icons/notification.svg new file mode 100644 index 000000000..462ba7e9b --- /dev/null +++ b/app/assets/images/icons/notification.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/photo.svg b/app/assets/images/icons/photo.svg new file mode 100644 index 000000000..f002aa3f4 --- /dev/null +++ b/app/assets/images/icons/photo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/plant-seeds.svg b/app/assets/images/icons/plant-seeds.svg deleted file mode 100644 index 86fc2a6c7..000000000 --- a/app/assets/images/icons/plant-seeds.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/app/assets/images/icons/plant_parts/bark.svg b/app/assets/images/icons/plant_parts/bark.svg new file mode 100644 index 000000000..f1f1af1ed --- /dev/null +++ b/app/assets/images/icons/plant_parts/bark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/plant_parts/bulb.svg b/app/assets/images/icons/plant_parts/bulb.svg new file mode 100644 index 000000000..48b62c4ce --- /dev/null +++ b/app/assets/images/icons/plant_parts/bulb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/plant_parts/flower.svg b/app/assets/images/icons/plant_parts/flower.svg new file mode 100644 index 000000000..2aed72c48 --- /dev/null +++ b/app/assets/images/icons/plant_parts/flower.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/plant_parts/fruit.svg b/app/assets/images/icons/plant_parts/fruit.svg new file mode 100644 index 000000000..115ac4bcb --- /dev/null +++ b/app/assets/images/icons/plant_parts/fruit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/plant_parts/leaves.svg b/app/assets/images/icons/plant_parts/leaves.svg new file mode 100644 index 000000000..3f7da11bd --- /dev/null +++ b/app/assets/images/icons/plant_parts/leaves.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/plant_parts/pod.svg b/app/assets/images/icons/plant_parts/pod.svg new file mode 100644 index 000000000..b06004f4c --- /dev/null +++ b/app/assets/images/icons/plant_parts/pod.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/plant_parts/seeds.svg b/app/assets/images/icons/plant_parts/seeds.svg new file mode 100644 index 000000000..c8652e798 --- /dev/null +++ b/app/assets/images/icons/plant_parts/seeds.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/plant_parts/tuber.svg b/app/assets/images/icons/plant_parts/tuber.svg new file mode 100644 index 000000000..2a96312c2 --- /dev/null +++ b/app/assets/images/icons/plant_parts/tuber.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/planting-add.svg b/app/assets/images/icons/planting-add.svg new file mode 100644 index 000000000..dd025e3c6 --- /dev/null +++ b/app/assets/images/icons/planting-add.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/app/assets/images/icons/planting-hand.svg b/app/assets/images/icons/planting-hand.svg new file mode 100644 index 000000000..03731c9a6 --- /dev/null +++ b/app/assets/images/icons/planting-hand.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/planting-sun.svg b/app/assets/images/icons/planting-sun.svg new file mode 100644 index 000000000..4b69d1d59 --- /dev/null +++ b/app/assets/images/icons/planting-sun.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/planting.svg b/app/assets/images/icons/planting.svg index 48d867aa0..bb37c3756 100644 --- a/app/assets/images/icons/planting.svg +++ b/app/assets/images/icons/planting.svg @@ -1,5 +1 @@ - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/assets/images/icons/post.svg b/app/assets/images/icons/post.svg index 2e8615513..7198c4607 100644 --- a/app/assets/images/icons/post.svg +++ b/app/assets/images/icons/post.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/app/assets/images/icons/rabbit.svg b/app/assets/images/icons/rabbit.svg new file mode 100644 index 000000000..ed73bb996 --- /dev/null +++ b/app/assets/images/icons/rabbit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/seed-add.svg b/app/assets/images/icons/seed-add.svg new file mode 100644 index 000000000..9040d3628 --- /dev/null +++ b/app/assets/images/icons/seed-add.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/app/assets/images/icons/seeds-colour.svg b/app/assets/images/icons/seeds-colour.svg deleted file mode 100644 index b2bc3e95b..000000000 --- a/app/assets/images/icons/seeds-colour.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/assets/images/icons/seeds.svg b/app/assets/images/icons/seeds.svg index e546336af..b2bc3e95b 100644 --- a/app/assets/images/icons/seeds.svg +++ b/app/assets/images/icons/seeds.svg @@ -1,5 +1,18 @@ - + - + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/assets/images/icons/slug-eating.svg b/app/assets/images/icons/slug-eating.svg new file mode 100644 index 000000000..37779e14d --- /dev/null +++ b/app/assets/images/icons/slug-eating.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/slug.svg b/app/assets/images/icons/slug.svg new file mode 100644 index 000000000..fd1c93e6e --- /dev/null +++ b/app/assets/images/icons/slug.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/snail.svg b/app/assets/images/icons/snail.svg new file mode 100644 index 000000000..bdeee93f0 --- /dev/null +++ b/app/assets/images/icons/snail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/spiderweb.svg b/app/assets/images/icons/spiderweb.svg new file mode 100644 index 000000000..634bf37ee --- /dev/null +++ b/app/assets/images/icons/spiderweb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/sprinkler.svg b/app/assets/images/icons/sprinkler.svg new file mode 100644 index 000000000..3458f029f --- /dev/null +++ b/app/assets/images/icons/sprinkler.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/sprout-add.svg b/app/assets/images/icons/sprout-add.svg new file mode 100644 index 000000000..e58159cd7 --- /dev/null +++ b/app/assets/images/icons/sprout-add.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/app/assets/images/icons/sprout.svg b/app/assets/images/icons/sprout.svg new file mode 100644 index 000000000..bfebc1bb5 --- /dev/null +++ b/app/assets/images/icons/sprout.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/stones.svg b/app/assets/images/icons/stones.svg new file mode 100644 index 000000000..06585a21b --- /dev/null +++ b/app/assets/images/icons/stones.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/timeline.svg b/app/assets/images/icons/timeline.svg new file mode 100644 index 000000000..e1e151dda --- /dev/null +++ b/app/assets/images/icons/timeline.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/images/icons/watering-can.svg b/app/assets/images/icons/watering-can.svg new file mode 100644 index 000000000..3e24501d1 --- /dev/null +++ b/app/assets/images/icons/watering-can.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/icons/wheelbarrow.svg b/app/assets/images/icons/wheelbarrow.svg new file mode 100644 index 000000000..97d319f29 --- /dev/null +++ b/app/assets/images/icons/wheelbarrow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/javascripts/crops.js.coffee b/app/assets/javascripts/crops.js.coffee index 6a0a27123..5e96b90e0 100644 --- a/app/assets/javascripts/crops.js.coffee +++ b/app/assets/javascripts/crops.js.coffee @@ -1,20 +1,20 @@ jQuery -> - $('#add-sci_name-row').css("display", "inline-block") - $('#remove-sci_name-row').css("display", "inline-block") - $("#add-alt_name-row").css("display", "inline-block") - $("#remove-alt_name-row").css("display", "inline-block") + $('.add-sciname-row').css("display", "inline-block") + $('.remove-sciname-row').css("display", "inline-block") + $(".add-altname-row").css("display", "inline-block") + $(".remove-altname-row").css("display", "inline-block") -$ -> sci_template = "Scientific name INDEX:Scientific name of crop." sci_index = $('#scientific_names .template').length + 1 - $('#add-sci_name-row').click -> + $('.add-sciname-row').click -> compiled_input = $(sci_template.split("INDEX").join(sci_index)) $('#scientific_names').append(compiled_input) sci_index = sci_index + 1 - $('#remove-sci_name-row').click -> + $('.remove-sciname-row').click -> if (sci_index > 2) sci_index = sci_index - 1 tmp = 'sci_template[' + sci_index + ']' @@ -25,12 +25,12 @@ jQuery -> alt_index = $('#alternate_names .template').length + 1 - $('#add-alt_name-row').click -> + $('.add-altname-row').click -> compiled_input = $(alt_template.split("INDEX").join(alt_index)) $('#alternate_names').append(compiled_input) alt_index = alt_index + 1 - $('#remove-alt_name-row').click -> + $('.remove-altname-row').click -> if (alt_index > 2) alt_index = alt_index - 1 tmp = 'alt_template[' + alt_index + ']' diff --git a/app/assets/javascripts/datepicker.js b/app/assets/javascripts/datepicker.js new file mode 100644 index 000000000..29778c169 --- /dev/null +++ b/app/assets/javascripts/datepicker.js @@ -0,0 +1,3 @@ +$(document).ready(function() { + $('.add-datepicker').datepicker({'format': 'yyyy-mm-dd'}); +}); diff --git a/app/assets/javascripts/editable.js b/app/assets/javascripts/editable.js new file mode 100644 index 000000000..e28846cd1 --- /dev/null +++ b/app/assets/javascripts/editable.js @@ -0,0 +1,6 @@ +$(document).ready(function() { + $('.editable').click(function() { + $(this.dataset.form).show(); + $(this.dataset.display).hide(); + }); +}); diff --git a/app/assets/javascripts/plantings.js.coffee b/app/assets/javascripts/plantings.js.coffee deleted file mode 100644 index ab5b73462..000000000 --- a/app/assets/javascripts/plantings.js.coffee +++ /dev/null @@ -1,6 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ - -jQuery -> - $('.add-datepicker').datepicker('format' : 'yyyy-mm-dd') diff --git a/app/assets/javascripts/seeds.js.coffee b/app/assets/javascripts/seeds.js.coffee deleted file mode 100644 index ab5b73462..000000000 --- a/app/assets/javascripts/seeds.js.coffee +++ /dev/null @@ -1,6 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ - -jQuery -> - $('.add-datepicker').datepicker('format' : 'yyyy-mm-dd') diff --git a/app/assets/stylesheets/_crops.scss b/app/assets/stylesheets/_crops.scss index 26fb947e6..4649812df 100644 --- a/app/assets/stylesheets/_crops.scss +++ b/app/assets/stylesheets/_crops.scss @@ -1,9 +1,52 @@ +.crop-icon { + height: 1em; +} + +.crop-thumbnail { + .text { + bottom: 0; + color: $white; + margin: 0; + opacity: .8; + position: absolute; + text-align: center; + width: 100%; + + h3 { + background-color: $brown; + font-size: 1em; + margin-bottom: 0; + padding-bottom: 0; + } + + h5.crop-sci-name { + background-color: $beige; + color: $black; + font-size: .7em; + margin-top: 0; + padding-top: 0; + } + + a { + color: $white; + } + + a:hover { + color: $black; + } + + .img-card { + border: 5%; + border-radius: 5%; + } + } +} + .planting { .crop-card { height: 100%; margin: .1em; min-height: 300px; - width: 100px; } .img-thumbnail { @@ -19,3 +62,31 @@ background-color: $white; padding: 1em; } + +.crop-hierarchy { + list-style-type: disc; +} + +.add-sciname-row, +.remove-sciname-row, +.add-altname-row, +.remove-altname-row { + display: none; +} + +.crop-chip { + background-color: lighten($brown, 20%); +} + +.crop-hero-photo { + max-height: 300px; +} + +@include media-breakpoint-down(xs) { + .index-cards { + .crop-thumbnail { + margin: .2em; + width: 30%; + } + } +} diff --git a/app/assets/stylesheets/_harvests.scss b/app/assets/stylesheets/_harvests.scss index e69de29bb..560983965 100644 --- a/app/assets/stylesheets/_harvests.scss +++ b/app/assets/stylesheets/_harvests.scss @@ -0,0 +1,34 @@ +.harvest-thumbnail { + .text { + color: $white; + margin: 0; + opacity: .9; + position: absolute; + text-align: center; + top: 50%; + width: 100%; + + h3 { + background-color: $green; + font-size: 1em; + } + + h5 { + background-color: $brown; + color: $white; + font-size: .7em; + } + + a { + color: $white; + } + + a:hover { + color: $black; + } + + .img-card { + border: 5%; + } + } +} diff --git a/app/assets/stylesheets/_homepage.scss b/app/assets/stylesheets/_homepage.scss index 17243ba8b..d33364c23 100644 --- a/app/assets/stylesheets/_homepage.scss +++ b/app/assets/stylesheets/_homepage.scss @@ -1,76 +1,17 @@ -// signup widget on homepage -.jumbotron .signup { - background-color: lighten($green, 40%); - border: 1px solid lighten($green, 20%); - border-radius: 6px; - line-height: 200%; - padding: 15px; - text-align: center; + +// stats shown on homepage. eg. "999 members..." +.stats { + font-weight: bold; } -.homepage-cards { - display: flex; - flex: none; - flex-wrap: wrap; - margin: .5em; - // left: -.5em; - - .card { - left: -.5em; - margin: .5em; - min-height: 100px; - padding: 0; - - %h3.crop-name { - font-size: 2em; - } - } - - .crop-card { - min-height: 80px; - width: 120px; - } - - .thumbnail { - margin: .5em; - } - - .img-card { - height: 150px; - } - -} - -.member-cards { - display: flex; - flex: none; - flex-wrap: wrap; - - .card { - margin: 1em; - width: 150px; +.crops, +.seeds, +.members { + .index-cards { + justify-content: center; } } - -@include media-breakpoint-down(sm) { - .homepage-cards { - .seed-card { - min-height: 80px; - width: 100%; - } - - .member-card { - min-height: 80px; - width: 150px; - } - } -} - -@include media-breakpoint-up(md) { - .homepage-cards { - .card { - width: 200px; - } - } +.homepage--list-item { + height: 100px; } diff --git a/app/assets/stylesheets/_maps.scss b/app/assets/stylesheets/_maps.scss new file mode 100644 index 000000000..b92ee2283 --- /dev/null +++ b/app/assets/stylesheets/_maps.scss @@ -0,0 +1,9 @@ +.map { + height: 500px; + z-index: 2; +} + +.member-map { + height: 250px; + z-index: 2; +} diff --git a/app/assets/stylesheets/_members.scss b/app/assets/stylesheets/_members.scss index 758aff496..188b4a317 100644 --- a/app/assets/stylesheets/_members.scss +++ b/app/assets/stylesheets/_members.scss @@ -19,22 +19,31 @@ .member-chip { background-color: lighten($green, 30%); - border-radius: 25px; - display: inline-block; - font-size: 1em; - height: 30px; - line-height: 30px; - padding: 0 25px; a { color: $black; } +} - img { - border-radius: 50%; - float: left; - height: 30px; - margin: 0 10px 0 -25px; - width: 30px; +.member-location { + font-size: small; + font-style: italic; + + a { + color: $brown; } } + +.location-not-set { + background-image: image-url('location-not-set.en.png'); + background-position: center; + background-repeat: no-repeat; + height: 250px; + width: 100%; +} + +.badge-location { + background-color: darken($blue, 10%); + border-radius: 5%; + color: $white; +} diff --git a/app/assets/stylesheets/notifications.scss b/app/assets/stylesheets/_notifications.scss similarity index 100% rename from app/assets/stylesheets/notifications.scss rename to app/assets/stylesheets/_notifications.scss diff --git a/app/assets/stylesheets/_photos.scss b/app/assets/stylesheets/_photos.scss index 5127f2336..831de851b 100644 --- a/app/assets/stylesheets/_photos.scss +++ b/app/assets/stylesheets/_photos.scss @@ -9,15 +9,6 @@ display: block; } -.photo-grid-item { - float: left; - - img { - display: block; - max-width: 100%; - } -} - .hero-photo { max-height: 500px; } diff --git a/app/assets/stylesheets/_plantings.scss b/app/assets/stylesheets/_plantings.scss index fadfb35a6..897631e83 100755 --- a/app/assets/stylesheets/_plantings.scss +++ b/app/assets/stylesheets/_plantings.scss @@ -26,14 +26,6 @@ font-size: 200%; } } - .progress { - .progress-bar { - border-bottom-color: $green; - } - .progress-bar:after { - background-color: $beige; - } - } .planting-name { position: relative; @@ -42,7 +34,6 @@ } .planting-quick-actions { - background-color: $white; left: 0; position: absolute; top: 0; @@ -62,35 +53,3 @@ } } } - - - -.planting-facts { - display: flex; - flex-wrap: wrap; - flex: none; - - .planting-fact-card { - background: $white; - background: $white; - border-radius: 5%; - border: 1px solid lighten($green, 20%); - margin: 1em; - padding: 1em; - text-align: center; - width: 120px; - - strong { - font-align: center; - font-size: 4em; - } - - span { - display: block; - } - - img { - height: 50%; - } - } -} diff --git a/app/assets/stylesheets/posts.scss b/app/assets/stylesheets/_posts.scss similarity index 54% rename from app/assets/stylesheets/posts.scss rename to app/assets/stylesheets/_posts.scss index d7f80d0aa..5eb8a4b77 100644 --- a/app/assets/stylesheets/posts.scss +++ b/app/assets/stylesheets/_posts.scss @@ -1,3 +1,7 @@ +.post-actions { + margin-bottom: 1rem; +} + .post-content { img { max-width: 100%; diff --git a/app/assets/stylesheets/_predictions.scss b/app/assets/stylesheets/_predictions.scss index f16c6ee8f..18d1ac623 100644 --- a/app/assets/stylesheets/_predictions.scss +++ b/app/assets/stylesheets/_predictions.scss @@ -9,7 +9,7 @@ strong { font-align: center; - font-size: 4em; + font-size: 3em; } span { diff --git a/app/assets/stylesheets/_seeds.scss b/app/assets/stylesheets/_seeds.scss index e69de29bb..e875832ff 100644 --- a/app/assets/stylesheets/_seeds.scss +++ b/app/assets/stylesheets/_seeds.scss @@ -0,0 +1,10 @@ +.seed-card { + .text { + color: $white; + margin: 0; + position: absolute; + text-align: center; + top: 1em; + width: 100%; + } +} diff --git a/app/assets/stylesheets/_variables.scss b/app/assets/stylesheets/_variables.scss index 574753da5..0f46481a7 100644 --- a/app/assets/stylesheets/_variables.scss +++ b/app/assets/stylesheets/_variables.scss @@ -11,11 +11,11 @@ $orange: #ffa500; $yellow: #b2935c; $white: #fff; - $body-bg: $beige; $text-color: $brown; $link-color: $green; -$graph-hover: $orange; + +$default-font: "Fira Sans", Helvetica, Arial, sans-serif; $primary: ( color: $green, @@ -43,6 +43,20 @@ $dark: ( light: lighten($brown, 20%) ); + +$grid-breakpoints: ( + // Extra small screen / phone + xs: 0, + // Small screen / phone + sm: 576px, + // Medium screen / tablet + md: 768px, + // Large screen / desktop + lg: 1200px, + // Extra large screen / wide desktop + xl: 1800px +); + // Nav bar $navbar-default-bg: $brown; $navbar-default-bg-highlight: $brown; @@ -53,3 +67,7 @@ $navbar-default-link-active-color: darken($beige, 80%); $navbar-default-brand-color: lighten($green, 20%); $highest-level: 1070; + +$progress-height: 5em; +$progress-bar-color: $green; +$progress-bg: $white; \ No newline at end of file diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 15c7d43c2..be50fd6a9 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -16,15 +16,17 @@ @import 'rails_bootstrap_forms'; @import 'overrides'; + @import 'crops'; @import 'harvests'; @import 'likes'; @import 'members'; @import 'notifications'; @import 'plantings'; +@import 'photos'; @import 'posts'; -@import 'predictions'; @import 'seeds'; +@import 'predictions'; @import 'homepage'; -@import 'photos'; +@import 'maps'; diff --git a/app/assets/stylesheets/overrides.scss b/app/assets/stylesheets/overrides.scss index 79db1fb4a..5b55739be 100755 --- a/app/assets/stylesheets/overrides.scss +++ b/app/assets/stylesheets/overrides.scss @@ -1,11 +1,11 @@ +html, body { - background-color: $beige; - font-family: $font-family-sans-serif; + height: 100%; } -section { - padding-top: 1em; - margin: 0; +body { + background-color: $beige; + font-family: $default-font; } .ellipsis { @@ -16,6 +16,11 @@ section { a { color: $green; + + .navbar-brand { + margin: 0; + padding: 0; + } } a:hover { @@ -23,18 +28,8 @@ a:hover { text-decoration: none; } -.card a:hover { - background-color: $beige; - color: $blue; -} - -.list-inline > li.first { - padding-left: 0; -} - -.activity-list { - list-style-type: none; - padding: 0; +h1 { + font-size: 250%; } h2 { @@ -45,10 +40,6 @@ h3 { font-size: 120%; } -.main { - padding-right: 1em; -} - #navbar-search { .input-group { input { @@ -62,166 +53,144 @@ h3 { } } +section { + margin: .5em 0 0; + padding: 0 0 1em; + + h2 { + background-color: $green; + box-shadow: 1px 1px 1px 1px darken($beige, 20%); + color: $white; + padding: .2em; + + a { + color: $white; + } + } + + .card { + box-shadow: 1px 3px 3px 1px darken($beige, 20%); + } +} + +.layout-actions { + border: 1px solid lighten($green, 20%); + float: right; +} + .index-cards { display: flex; flex: none; flex-wrap: wrap; .card { - margin: 1em 1em 1em 0; + background: $white; + border-radius: 5%; + margin: .5em .5em .5em 0; width: 200px; + + .img-card { + border-top-left-radius: 5%; + border-top-right-radius: 5%; + } } + .card-finished { + background: darken($beige, 10%); + a { + color: $brown; + } + } + + .card:hover { + background-color: $beige; + } +} + +.card { + margin-bottom: 1em; + + .img-card { + height: 200px; + object-fit: cover; + width: 100%; + } + + a:hover { + background-color: $beige; + color: $blue; + } +} + +.facts { + display: flex; + flex: none; + flex-wrap: wrap; + + .card { + margin: 1em; + max-width: 100%; + padding: 1em; + text-align: center; + width: 170px; + + strong { + display: block; + font-size: 2em; + text-align: center; + } + + span { + display: block; + } + + img { + height: 50%; + } + } + + .thumbnail { + height: 100px; + img { + border-radius: 5px; + height: 75px; + } + } +} + +img.img-cute { + bottom: -6px; + left: 45px; + position: relative; + width: 3em; +} + +img.img-tiny { + width: 50px; } img.img-icon { width: 1.2em; } -.img-square { - height: 150px; - object-fit: cover; - width: 150px; -} - -.img-card { - height: 180px; - object-fit: cover; - width: 100%; -} - -.img-responsive { +img.img-responsive { max-width: 100%; } -.avatar { +img.avatar { border-radius: 50%; padding: 1em; position: relative; z-index: 2; } -.profile-sidebar { - margin-top: -5rem; +.progress-bar { + border-bottom: 1em solid $progress-bar-color; } -.profile-activity { - background: $white; - margin-top: 2em; - padding: 2em; -} - -.sidebar { - border-left: 1px solid darken($beige, 10%); - margin-left: -1px; - padding-left: 1em; -} - -// this is used for eg. crops and members index pages -.six-across:nth-child(6n+1) { - margin-left: 0; -} - -.three-across:nth-child(3n+1) { - clear: both; - margin-left: 0; -} - -// let's condense the hero unit a little -.jumbotron { - background-color: darken($beige, 10%); - padding-top: 30px; - padding-bottom: 30px; - // signup widget on homepage - .signup { - background-color: lighten($green, 40%); - border-radius: 6px; - border: 1px solid lighten($green, 20%); - line-height: 200%; - padding: 15px; - text-align: center; - } -} - -// info under the main heading on homepage -.jumbotron .info { - //padding-top: 15px - padding: 0.5em; - text-align: center; -} - - -// stats shown on homepage. eg. "999 members..." -p.stats { - font-weight: bold; -} - -.card-row { - display: grid; - grid-gap: 25px; - grid-row-gap: 5px; - grid-template-columns: 50% 50%; -} -.card { - margin-bottom: 1em -} - -.progress { - border-radius: 0; - - .progress-bar-text { - text-align: center; - } -} - -#placesmap, -#cropmap { - height: 500px; -} - -#membermap { - height: 250px; - z-index: 0; -} - -.location-not-set { - background-image: image-url('location-not-set.en.png'); - background-position: center; - background-repeat: no-repeat; - height: 250px; - width: 100%; -} - -.member-location { - font-size: small; - font-style: italic; -} - -.member-location a { - color: $brown; -} - -.associations { +ul.associations { list-style-type: none; } - -li.crop-hierarchy { - list-style-type: disc; -} - -.navbar-brand { - margin: 0; - padding: 0; -} - -.navbar-bottom { - margin: 40px 0 0 !important; -} - -.post-actions { - margin-bottom: 1rem; -} - // footer footer { #footer1, @@ -254,22 +223,10 @@ footer { } // ensure footer is pushed to bottom of browser window - #maincontainer { + max-width: 2500px; min-height: 80%; -} - -#global-actions { - padding-top: 10px; -} - -html, -body { - height: 100%; -} - -.member-image { - border-radius: 50%; + padding: 1em; } // Autosuggest @@ -298,17 +255,6 @@ $state-success-bg: lighten($green, 50%); display: none; } -#add-sci_name-row, -#remove-sci_name-row, -#add-alt_name-row, -#remove-alt_name-row { - display: none; -} - -.panel-footer { - height: 6em; -} - .panel { .dl-horizontal { text-overflow: ellipsis; @@ -321,14 +267,6 @@ label.required:after { content:" *"; } -.margin-bottom { - margin-bottom: 1em; -} - -.red { - color: red; -} - .truncate { overflow: hidden; text-overflow: ellipsis; @@ -353,34 +291,6 @@ ul.thumbnail-buttons { visibility: visible; } -.homepage-listing { - padding-bottom: 6px; -} - -.container { - max-width: 1500px; -} - -// Small devices (landscape phones, less than 768px) -@include media-breakpoint-down(md) { - .planting-thumbnail { - dl.planting-attributes { - width: 100%; - - dt { - text-align: left; - width: 120px; - } - - dd { - margin-left: auto; - padding-left: 120px; - } - } - } -} - - .form-page { text-align: center; @@ -391,10 +301,78 @@ ul.thumbnail-buttons { padding: 1em; text-align: center; } +} - .crop-card { - width: 100px; +.jumbotron { + background-color: $beige; + margin-bottom: 1em; + padding-bottom: 30px; + padding-top: 30px; + + // signup widget on homepage + .signup { + background-color: lighten($green, 40%); + border: 1px solid lighten($green, 20%); + border-radius: 6px; + line-height: 200%; + padding: 15px; + text-align: center; } + + .info { + padding: .5em; + text-align: center; + } +} + +.breadcrumb { + background-color: $beige; +} + +.chip { + background-color: lighten($brown, 20%); + border-radius: 25px; + color: $white; + display: inline-block; + height: 30px; + line-height: 30px; + margin: .2em; + padding: 0 25px; + + a { + color: $white; + } + + img { + border-radius: 50%; + float: left; + height: 30px; + margin: 0 10px 0 -25px; + width: 30px; + } +} + +.progress-fade { + cursor: pointer; + float: left; + position: relative; +} + +.progress-fade::before { + background: $beige; + bottom: 0; + content: ''; + display: block; + left: 0; + opacity: .7; + position: absolute; + right: 0; + top: 0; + transition: background .3s linear; +} + +.progress-fade:hover::before { + background: none; } // Overrides applying only to mobile view. This must be at the end of the overrides file. @@ -402,6 +380,23 @@ ul.thumbnail-buttons { @include media-breakpoint-down(xs) { #maincontainer { width: 100%; + padding: 10px; + } + + h1 { + font-size: 200%; + } + + h2 { + font-size: 130%; + } + + h3 { + font-size: 120%; + } + + .card-title { + margin-bottom: 0; } .sidebar { @@ -410,7 +405,7 @@ ul.thumbnail-buttons { padding-left: 0; } - #map { + .map { height: 300px; } @@ -427,20 +422,32 @@ ul.thumbnail-buttons { } } - #maincontainer { - padding: 10px; + .nav .btn { + background-color: lighten($green, 50%); + width: 100%; } - .homepage-cards { - .crop-card { - width: 50px; - } + section .btn { + width: 100%; } .index-cards { .card { - width: 100%; - margin: 0.5em; + margin: .2em; + width: 48%; + + // Shrink title to fit more on page + .card-title { + font-size: 1em; + } + + // Restrict height of image when on smaller screens + .img-card { + height: 100px; + object-fit: cover; + width: 100%; + } + } } } diff --git a/app/controllers/admin/members_controller.rb b/app/controllers/admin/members_controller.rb index 483a3b699..0526a57c0 100644 --- a/app/controllers/admin/members_controller.rb +++ b/app/controllers/admin/members_controller.rb @@ -1,6 +1,9 @@ module Admin class MembersController < ApplicationController - before_action :auth! + before_action :admin! + load_and_authorize_resource + respond_to :html + responders :flash def index @members = Member.all @@ -14,13 +17,24 @@ module Admin redirect_to admin_members_path end + def edit + @member = Member.find_by!(slug: params[:slug]) + end + + def update + @member = Member.find_by!(slug: params[:slug]) + @member.update(roles: Role.where(id: params.require(:member).require(:role_ids))) + + respond_with @member, location: admin_members_path + end + private def search_term params[:q] end - def auth! + def admin! authorize! :manage, :all end end diff --git a/app/controllers/admin/roles_controller.rb b/app/controllers/admin/roles_controller.rb new file mode 100644 index 000000000..d08c292dc --- /dev/null +++ b/app/controllers/admin/roles_controller.rb @@ -0,0 +1,47 @@ +module Admin + class RolesController < ApplicationController + before_action :admin! + load_and_authorize_resource + respond_to :html + responders :flash + + def index + @roles = Role.all.order(:name) + respond_with @roles + end + + def new + @role = Role.new + respond_with @role + end + + def edit + respond_with @role + end + + def create + @role = Role.create(role_params) + respond_with @role, location: admin_roles_path + end + + def update + @role.update(role_params) + respond_with @role, location: admin_roles_path + end + + def destroy + @role.destroy + respond_with @role, location: admin_roles_path + end + + private + + def admin! + authorize! :manage, :all + end + + def role_params + params.require(:role).permit(:description, :name, :members, :slug) + end + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 023603b33..e30fe25b1 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -15,7 +15,7 @@ class ApplicationController < ActionController::Base "/members/password/edit", "/members/confirmation", "/members/sign_out"]) || request.xhr? - store_location_for(:member, request.fullpath) + store_location_for(:member, request.fullpath) if request.format == :html end end diff --git a/app/controllers/charts/gardens_controller.rb b/app/controllers/charts/gardens_controller.rb index 8b67bc106..b9d25bb53 100644 --- a/app/controllers/charts/gardens_controller.rb +++ b/app/controllers/charts/gardens_controller.rb @@ -7,7 +7,7 @@ module Charts @garden.plantings.where.not(planted_at: nil) .order(finished_at: :desc).each do |p| # use finished_at if we have it, otherwise use predictions - finish = p.finished_at.presence || p.finish_predicted_at + finish = p.finished_at.presence || p.finish_predicted_at || Time.zone.today.to_date @data << [p.crop.name, p.planted_at, finish] if finish.present? end render json: @data diff --git a/app/controllers/conversations_controller.rb b/app/controllers/conversations_controller.rb index 67cf92211..a8b806a9d 100644 --- a/app/controllers/conversations_controller.rb +++ b/app/controllers/conversations_controller.rb @@ -33,6 +33,18 @@ class ConversationsController < ApplicationController redirect_to conversations_path(box: params[:box]) end + def destroy_multiple + conversations = if @box.eql? 'sent' + mailbox.sentbox + else + mailbox.inbox + end + conversations.where(id: params[:conversation_ids]).each do |conversation| + conversation.move_to_trash(current_member) + end + redirect_to conversations_path(box: params[:box]) + end + private def mailbox diff --git a/app/controllers/crops_controller.rb b/app/controllers/crops_controller.rb index 8a02e195c..2dd6a948c 100644 --- a/app/controllers/crops_controller.rb +++ b/app/controllers/crops_controller.rb @@ -4,7 +4,7 @@ class CropsController < ApplicationController before_action :authenticate_member!, except: %i(index hierarchy search show) load_and_authorize_resource skip_authorize_resource only: %i(hierarchy search) - respond_to :html, :json, :rss, :csv + respond_to :html, :json, :rss, :csv, :svg responders :flash def index @@ -35,8 +35,14 @@ class CropsController < ApplicationController respond_with @crops end + def openfarm + @crop = Crop.find(params[:crop_slug]) + @crop.update_openfarm_data! + respond_with @crop, location: @crop + end + def hierarchy - @crops = Crop.toplevel + @crops = Crop.toplevel.order(:name) respond_with @crops end @@ -45,7 +51,7 @@ class CropsController < ApplicationController @crops = CropSearchService.search( @term, page: params[:page], - per_page: 12, + per_page: 36, current_member: current_member ) respond_with @crops @@ -60,6 +66,7 @@ class CropsController < ApplicationController respond_to do |format| format.html + format.svg { send_data(@crop.svg_icon, type: "image/svg+xml", disposition: "inline") } format.json { render json: @crop.to_json(crop_json_fields) } end end @@ -95,15 +102,23 @@ class CropsController < ApplicationController def update @crop = Crop.find_by!(slug: params[:slug]) - previous_status = @crop.approval_status - @crop.creator = current_member if previous_status == "pending" + if can?(:wrangle, @crop) + @crop.approval_status = 'rejected' if params.fetch("reject", false) + @crop.approval_status = 'approved' if params.fetch("approve", false) + end + @crop.creator = current_member if @crop.approval_status == "pending" if @crop.update(crop_params) recreate_names('alt_name', 'alternate') recreate_names('sci_name', 'scientific') - notifier.deliver_now! if previous_status == "pending" + if @crop.approval_status_changed?(from: "pending", to: "approved") + notifier.deliver_now! + @crop.update_openfarm_data! + end + else + @crop.approval_status = @crop.approval_status_was end respond_with @crop @@ -162,18 +177,18 @@ class CropsController < ApplicationController end def crop_params - params.require(:crop).permit(:en_wikipedia_url, + params.require(:crop).permit( + :en_wikipedia_url, :name, :parent_id, - :creator_id, :perennial, - :approval_status, :request_notes, :reason_for_rejection, :rejection_notes, scientific_names_attributes: %i(scientific_name _destroy - id)) + id) + ) end def filename diff --git a/app/controllers/follows_controller.rb b/app/controllers/follows_controller.rb index 15546742f..1555a4a7c 100644 --- a/app/controllers/follows_controller.rb +++ b/app/controllers/follows_controller.rb @@ -1,5 +1,4 @@ class FollowsController < ApplicationController - before_action :authenticate_member! before_action :set_member, only: %i(index followers) load_and_authorize_resource skip_load_resource only: :create diff --git a/app/controllers/gardens_controller.rb b/app/controllers/gardens_controller.rb index f9037923a..8a39b6dc1 100644 --- a/app/controllers/gardens_controller.rb +++ b/app/controllers/gardens_controller.rb @@ -5,59 +5,46 @@ class GardensController < ApplicationController responders :flash respond_to :html, :json - # GET /gardens - # GET /gardens.json def index @owner = Member.find_by(slug: params[:member_slug]) @show_all = params[:all] == '1' @gardens = @gardens.active unless @show_all @gardens = @gardens.where(owner: @owner) if @owner.present? - @gardens = @gardens.joins(:owner).order(:name).paginate(page: params[:page]) + @gardens = @gardens.where.not(members: { confirmed_at: nil }) + .order(:name).paginate(page: params[:page]) respond_with(@gardens) end - # GET /gardens/1 - # GET /gardens/1.json def show - @current_plantings = @garden.plantings.current - .includes(:crop, :owner) - .order(planted_at: :desc) - @finished_plantings = @garden.plantings.finished - .includes(:crop) - .order(finished_at: :desc) + @current_plantings = @garden.plantings.current.includes(:crop, :owner).order(planted_at: :desc) + @finished_plantings = @garden.plantings.finished.includes(:crop) + @suggested_companions = Crop.approved.where( + id: CropCompanion.where(crop_a_id: @current_plantings.select(:crop_id)).select(:crop_b_id) + ).order(:name) respond_with(@garden) end - # GET /gardens/new - # GET /gardens/new.json def new @garden = Garden.new respond_with(@garden) end - # GET /gardens/1/edit def edit respond_with(@garden) end - # POST /gardens - # POST /gardens.json def create @garden.owner_id = current_member.id flash[:notice] = I18n.t('gardens.created') if @garden.save respond_with(@garden) end - # PUT /gardens/1 - # PUT /gardens/1.json def update flash[:notice] = I18n.t('gardens.updated') if @garden.update(garden_params) respond_with(@garden) end - # DELETE /gardens/1 - # DELETE /gardens/1.json def destroy @garden.destroy flash[:notice] = I18n.t('gardens.deleted') diff --git a/app/controllers/harvests_controller.rb b/app/controllers/harvests_controller.rb index f1c98fe86..353e338d6 100644 --- a/app/controllers/harvests_controller.rb +++ b/app/controllers/harvests_controller.rb @@ -43,7 +43,11 @@ class HarvestsController < ApplicationController @harvest.crop_id = @harvest.planting.crop_id if @harvest.planting_id @harvest.harvested_at = Time.zone.now if @harvest.harvested_at.blank? @harvest.save - respond_with(@harvest) + if params[:return] == 'planting' + respond_with(@harvest, location: @harvest.planting) + else + respond_with(@harvest) + end end def update diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index fe3f84d02..c8908801e 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -29,8 +29,8 @@ class PhotosController < ApplicationController def new @photo = Photo.new @item = item_to_link_to - @type = item_type - @id = item_id + @type = params[:type] + @id = params[:id] retrieve_from_flickr respond_with @photo end @@ -63,31 +63,17 @@ class PhotosController < ApplicationController private - # - # Params - def item_id - params[:id] - end - - def item_type - params[:type] - end - - def flickr_photo_id_param - params[:photo][:flickr_photo_id] - end - def photo_params - params.require(:photo).permit(:flickr_photo_id, :title, :license_name, + params.require(:photo).permit(:source_id, :source, :title, :license_name, :license_url, :thumbnail_url, :fullsize_url, :link_url) end # Item with photos attached def item_to_link_to - raise "No item id provided" if item_id.nil? - raise "No item type provided" if item_type.nil? + raise "No item id provided" if params[:id].nil? + raise "No item type provided" if params[:type].nil? - item_class = item_type.capitalize + item_class = params[:type].capitalize raise "Photos not supported" unless Photo::PHOTO_CAPABLE.include? item_class item_class.constantize.find(params[:id]) @@ -96,8 +82,11 @@ class PhotosController < ApplicationController # # Flickr retrieval def find_or_create_photo_from_flickr_photo - photo = Photo.find_by(flickr_photo_id: flickr_photo_id_param) - photo ||= Photo.new(photo_params) + photo = Photo.find_or_initialize_by( + source_id: photo_params[:source_id], + source: 'flickr' + ) + photo.update(photo_params) photo.owner_id = current_member.id photo.set_flickr_metadata! photo diff --git a/app/controllers/plantings_controller.rb b/app/controllers/plantings_controller.rb index 1bbbefc93..d96c7f266 100644 --- a/app/controllers/plantings_controller.rb +++ b/app/controllers/plantings_controller.rb @@ -32,6 +32,7 @@ class PlantingsController < ApplicationController def show @photos = @planting.photos.includes(:owner).order(date_taken: :desc) + @matching_seeds = matching_seeds respond_with @planting end @@ -61,6 +62,7 @@ class PlantingsController < ApplicationController def create @planting = Planting.new(planting_params) + @planting.planted_at = Time.zone.now if @planting.planted_at.blank? @planting.owner = current_member @planting.crop = @planting.parent_seed.crop if @planting.parent_seed.present? @planting.save @@ -112,6 +114,12 @@ class PlantingsController < ApplicationController .paginate(page: params[:page]) end + def matching_seeds + Seed.where(crop: @planting.crop, owner: @planting.owner) + .where('(finished_at IS NULL OR finished_at >= ?)', @planting.planted_at) + .where('(saved_at IS NULL OR saved_at <= ?)', @planting.planted_at) + end + def specifics if @owner.present? "#{@owner.to_param}-" diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 0180b1b8d..d6561ce8e 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -5,36 +5,25 @@ class PostsController < ApplicationController respond_to :html, :json respond_to :rss, only: %i(index show) - # GET /posts - # GET /posts.json - # GET /posts.rss def index @author = Member.find_by(slug: params[:member_slug]) @posts = posts respond_with(@posts) end - # GET /posts/1 - # GET /posts/1.json - # GET /posts/1.rss def show @post = Post.includes(:author, comments: :author).find(params[:id]) respond_with(@post) end - # GET /posts/new - # GET /posts/new.json def new @post = Post.new @forum = Forum.find_by(id: params[:forum_id]) respond_with(@post) end - # GET /posts/1/edit def edit; end - # POST /posts - # POST /posts.json def create params[:post][:author_id] = current_member.id @post = Post.new(post_params) @@ -42,15 +31,11 @@ class PostsController < ApplicationController respond_with(@post) end - # PUT /posts/1 - # PUT /posts/1.json def update flash[:notice] = 'Post was successfully updated.' if @post.update(post_params) respond_with(@post) end - # DELETE /posts/1 - # DELETE /posts/1.json def destroy flash[:notice] = 'Post was deleted.' if @post.destroy respond_with(@post) @@ -67,6 +52,6 @@ class PostsController < ApplicationController @author.posts else Post - end.order(created_at: :desc).includes(:author, comments: :author).paginate(page: params[:page]) + end.order(created_at: :desc).includes(:author, comments: :author).paginate(page: params[:page], per_page: 12) end end diff --git a/app/controllers/roles_controller.rb b/app/controllers/roles_controller.rb deleted file mode 100644 index 6b43cf9da..000000000 --- a/app/controllers/roles_controller.rb +++ /dev/null @@ -1,45 +0,0 @@ -class RolesController < ApplicationController - before_action :authenticate_member! - load_and_authorize_resource - respond_to :html - responders :flash - - def index - @roles = Role.all.order(:name) - respond_with @roles - end - - def show - respond_with @role - end - - def new - @role = Role.new - respond_with @role - end - - def edit - respond_with @role - end - - def create - @role = Role.create(role_params) - respond_with @role - end - - def update - @role.update(role_params) - respond_with @role - end - - def destroy - @role.destroy - respond_with @role - end - - private - - def role_params - params.require(:role).permit(:description, :name, :members, :slug) - end -end diff --git a/app/controllers/seeds_controller.rb b/app/controllers/seeds_controller.rb index 1989db825..b6457edbf 100644 --- a/app/controllers/seeds_controller.rb +++ b/app/controllers/seeds_controller.rb @@ -6,19 +6,15 @@ class SeedsController < ApplicationController respond_to :csv, only: :index respond_to :rss, only: :index - # GET /seeds - # GET /seeds.json def index @owner = Member.find_by(slug: params[:member_slug]) if params[:member_slug].present? @crop = Crop.find_by(slug: params[:crop_slug]) if params[:crop_slug].present? @planting = Planting.find_by(slug: params[:planting_id]) if params[:planting_id].present? - @seeds = @seeds.where(owner: @owner) if @owner.present? - @seeds = @seeds.where(crop: @crop) if @crop.present? - @seeds = @seeds.where(parent_planting: @planting) if @planting.present? - @seeds = @seeds.order(created_at: :desc).includes(:owner, :crop).paginate(page: params[:page]) + @show_all = (params[:all] == '1') @filename = csv_filename + @seeds = seeds.order(created_at: :desc).includes(:owner, :crop).paginate(page: params[:page]) respond_with(@seeds) end @@ -46,7 +42,11 @@ class SeedsController < ApplicationController @seed.owner = current_member @seed.crop = @seed.parent_planting.crop if @seed.parent_planting flash[:notice] = "Successfully added #{@seed.crop} seed to your stash." if @seed.save - respond_with(@seed) + if params[:return] == 'planting' + respond_with(@seed, location: @seed.parent_planting) + else + respond_with(@seed) + end end def update @@ -61,10 +61,19 @@ class SeedsController < ApplicationController private + def seeds + records = Seed.all + records = records.where(owner: @owner) if @owner.present? + records = records.where(crop: @crop) if @crop.present? + records = records.where(parent_planting: @planting) if @planting.present? + records = records.active unless @show_all + records + end + def seed_params params.require(:seed).permit( :crop_id, :description, :quantity, :plant_before, - :parent_planting_id, + :parent_planting_id, :saved_at, :days_until_maturity_min, :days_until_maturity_max, :organic, :gmo, :heirloom, :tradable_to, :slug, diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 47870d38d..b8de4b54e 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,5 +1,5 @@ class SessionsController < Devise::SessionsController - respond_to :json + respond_to :html, :json def create super do |_resource| diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 29f0ab05b..63a4b45c5 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -54,7 +54,8 @@ module ApplicationHelper end Gravatar.new(member.email).image_url(size: size, - default: :identicon) + default: :identicon, + ssl: true) end # Returns a string with the quantity and the right pluralization for a @@ -63,15 +64,15 @@ module ApplicationHelper pluralize(collection.size, model.model_name.to_s.downcase) end - def show_inactive_tickbox_path(type, owner, show_all) + def show_inactive_tickbox_path(type, owner: nil, show_all: false) all = show_all ? '' : 1 - if owner - return member_plantings_path(owner, all: all) if type == 'plantings' - return member_gardens_path(owner, all: all) if type == 'gardens' - end - return plantings_path(all: all) if type == 'plantings' - return gardens_path(all: all) if type == 'gardens' + path = if owner.present? + public_send("member_#{type}_path", owner, all: all) + else + public_send("#{type}_path", all: all) + end + path end def title(type, owner, crop, planting) diff --git a/app/helpers/buttons_helper.rb b/app/helpers/buttons_helper.rb index cbcab26a3..05fc2798d 100644 --- a/app/helpers/buttons_helper.rb +++ b/app/helpers/buttons_helper.rb @@ -107,7 +107,7 @@ module ButtonsHelper link_to new_photo_path(id: model.id, type: model_type_for_photo(model)), class: classes do - photo_icon + ' ' + t('buttons.add_photo') + add_photo_icon + ' ' + t('buttons.add_photo') end end diff --git a/app/helpers/editable_form_helper.rb b/app/helpers/editable_form_helper.rb new file mode 100644 index 000000000..00cabebad --- /dev/null +++ b/app/helpers/editable_form_helper.rb @@ -0,0 +1,6 @@ +module EditableFormHelper + def editable(field_type, model, field, display_field:, collection: []) + render 'shared/editable/form', field_type: field_type, + model: model, field: field, display_field: display_field, collection: collection + end +end diff --git a/app/helpers/event_helper.rb b/app/helpers/event_helper.rb index 4900c14d1..f2ff4a5fe 100644 --- a/app/helpers/event_helper.rb +++ b/app/helpers/event_helper.rb @@ -1,4 +1,8 @@ module EventHelper + def in_weeks(days) + (days / 7.0).round + end + def event_description(event) render "#{event.event_type.pluralize}/description", event_model: resolve_model(event) end diff --git a/app/helpers/gardens_helper.rb b/app/helpers/gardens_helper.rb index fd406431e..1ebb589f0 100644 --- a/app/helpers/gardens_helper.rb +++ b/app/helpers/gardens_helper.rb @@ -9,10 +9,6 @@ module GardensHelper end end - def gardens_active_tickbox_path(owner, show_all) - show_inactive_tickbox_path('gardens', owner, show_all) - end - def display_garden_name(garden) truncate(garden.name, length: 50, separator: ' ', omission: '... ') end diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index a691b5218..e2f49ca7d 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -5,12 +5,20 @@ module IconsHelper send("#{event_model}_icon") end + def cute_icon + icons = %w(slug sprinkler bee ant hose grass rabbit slug-eating snail earth-worm insect watering-can + wheelbarrow cat spiderweb bug butterfly ladybird stones) + rand_num = rand(1..icons.size) + icon = icons[rand_num - 1] + image_tag("icons/#{icon}.svg", class: 'img img-cute', alt: icon) + end + def timeline_icon - icon('far', 'calendar') + image_icon 'timeline' end def garden_icon - image_icon 'home' + image_icon 'gardens' end def planting_icon @@ -25,6 +33,10 @@ module IconsHelper image_icon 'harvest' end + def growing_icon + image_icon 'growing' + end + def seed_icon image_icon 'seeds' end @@ -34,19 +46,23 @@ module IconsHelper end def finished_icon - icon('fas', 'calendar') + image_icon 'finish' end def edit_icon - icon('fas', 'edit') + image_icon 'edit' end def delete_icon - icon('fas', 'trash-alt') + image_icon 'delete' + end + + def add_photo_icon + image_icon 'add-photo' end def photo_icon - icon('fas', 'camera-retro') + image_icon 'photo' end def seedling_icon @@ -73,11 +89,26 @@ module IconsHelper icon('fas', 'heart') end - def sunniness_icon(sunniness) - if sunniness.present? - image_tag("sunniness_#{sunniness}.png", class: 'img', alt: sunniness, width: 55) + def crop_icon(crop) + if crop.svg_icon.present? + image_tag(crop_path(crop, format: 'svg'), class: 'crop-icon') + elsif crop.parent.present? + crop_icon(crop.parent) else - image_tag("sunniness_not_specified.png", class: 'img', alt: 'unknown', width: 55) + planting_icon + end + end + + def sunniness_icon(sunniness) + case sunniness + when 'sun' + icon 'far', 'sun' + when 'shade' + icon 'fas', 'umbrella-beach' + when 'semi-shade' + icon 'fas', 'cloud-sun' + else + icon 'fas', 'question' end end diff --git a/app/helpers/photos_helper.rb b/app/helpers/photos_helper.rb index 9e1053820..dc6d88348 100644 --- a/app/helpers/photos_helper.rb +++ b/app/helpers/photos_helper.rb @@ -1,37 +1,47 @@ module PhotosHelper - def crop_image_path(crop, full_size: false) - photo_or_placeholder(crop, full_size: full_size) - end - - def garden_image_path(garden, full_size: false) - photo_or_placeholder(garden, full_size: full_size) - end - - def planting_image_path(planting, full_size: false) - photo_or_placeholder(planting, full_size: full_size) - end - - def harvest_image_path(harvest, full_size: false) - photo_or_placeholder(harvest, full_size: full_size) - end - - def seed_image_path(seed, full_size: false) - photo_or_placeholder(seed, full_size: full_size) - end - - private - - def photo_or_placeholder(item, full_size: false) - if item.default_photo.present? - item_photo(item, full_size: full_size) + def crop_image_path(crop) + if crop.default_photo.present? + # The flickr thumbnails are too small, use full size + if crop.default_photo.source == 'flickr' + crop.default_photo.fullsize_url + else + crop.default_photo.thumbnail_url + end else placeholder_image end end - def item_photo(item, full_size:) - photo = item.default_photo - full_size ? photo.fullsize_url : photo.thumbnail_url + def garden_image_path(garden) + photo_or_placeholder(garden) + end + + def planting_image_path(planting) + photo_or_placeholder(planting) + end + + def harvest_image_path(harvest) + photo_or_placeholder(harvest) + end + + def seed_image_path(seed) + photo_or_placeholder(seed) + end + + private + + def photo_or_placeholder(item) + if item.default_photo.present? + item_photo(item) + elsif item.respond_to?(:crop) + crop_image_path(item.crop) + else + placeholder_image + end + end + + def item_photo(item) + item.default_photo.fullsize_url end def placeholder_image diff --git a/app/helpers/plantings_helper.rb b/app/helpers/plantings_helper.rb index 537ae7631..4de9102ed 100644 --- a/app/helpers/plantings_helper.rb +++ b/app/helpers/plantings_helper.rb @@ -29,10 +29,6 @@ module PlantingsHelper end end - def plantings_active_tickbox_path(owner, show_all) - show_inactive_tickbox_path('plantings', owner, show_all) - end - def days_from_now_to_finished(planting) return unless planting.finish_is_predicatable? diff --git a/app/mailers/notifier.rb b/app/mailers/notifier.rb index e39ced825..da8618cf9 100644 --- a/app/mailers/notifier.rb +++ b/app/mailers/notifier.rb @@ -25,15 +25,34 @@ class Notifier < ApplicationMailer def planting_reminder(member) @member = member + @sitename = ENV['GROWSTUFF_SITE_NAME'] - @plantings = @member.plantings.order(planted_at: :desc).first(5) - @harvests = @member.harvests.order(harvested_at: :desc).first(5) + @late = [] + @super_late = [] + @harvesting = [] + @others = [] + + @member.plantings.active.annual.each do |planting| + if planting.finish_is_predicatable? + if planting.super_late? + @super_late << planting + elsif planting.late? + @late << planting + elsif planting.harvest_time? + @harvesting << planting + else + @others << planting + end + end + end + + @subject = "Your #{Date.today.strftime('%B %Y')} #{@sitename} progress report" # Encrypting message = { member_id: @member.id, type: :send_planting_reminder } @signed_message = verifier.generate(message) - mail(to: @member.email, subject: "What have you planted lately?") if @member.send_planting_reminder + mail(to: @member.email, subject: @subject) if @member.send_planting_reminder end def new_crop_request(member, request) diff --git a/app/models/ability.rb b/app/models/ability.rb index 66c48a8c4..b2231e64b 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -12,8 +12,8 @@ class Ability # everyone can do these things, even non-logged in can :read, :all - can :view_follows, Member - can :view_followers, Member + can :read, Follow + can :followers, Follow # Everyone can see the charts can :timeline, Garden @@ -76,6 +76,7 @@ class Ability can :manage, Crop can :manage, ScientificName can :manage, AlternateName + can :openfarm, Crop end # any member can create a crop provisionally diff --git a/app/models/application_record.rb b/app/models/application_record.rb index 31fc9c587..e14f64e66 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -1,4 +1,4 @@ class ApplicationRecord < ActiveRecord::Base self.abstract_class = true - self.per_page = 12 + self.per_page = 36 end diff --git a/app/models/concerns/likeable.rb b/app/models/concerns/likeable.rb index 4937af53e..9db3da6e5 100644 --- a/app/models/concerns/likeable.rb +++ b/app/models/concerns/likeable.rb @@ -2,7 +2,7 @@ module Likeable extend ActiveSupport::Concern included do - has_many :likes, as: :likeable, inverse_of: :likeable, dependent: :destroy + has_many :likes, as: :likeable, inverse_of: :likeable, dependent: :delete_all has_many :members, through: :likes end diff --git a/app/models/concerns/open_farm_data.rb b/app/models/concerns/open_farm_data.rb new file mode 100644 index 000000000..f56c0fa66 --- /dev/null +++ b/app/models/concerns/open_farm_data.rb @@ -0,0 +1,75 @@ +module OpenFarmData + extend ActiveSupport::Concern + + included do # rubocop:disable Metrics/BlockLength + def update_openfarm_data! + OpenfarmService.new.update_crop(self) + end + + def of_photo + fetch_attr('main_image_path') + end + + def height + fetch_attr('height') + end + + def spread + fetch_attr('spread') + end + + def svg_icon + fetch_attr('svg_icon') + end + + def tags_array + fetch_attr('tags_array') + end + + def description + fetch_attr('description') + end + + def row_spacing + fetch_attr('row_spacing') + end + + def common_names + fetch_attr('common_names') + end + + def guides_count + fetch_attr('guides_count') + end + + def binomial_name + fetch_attr('binomial_name') + end + + def sowing_method + fetch_attr('sowing_method') + end + + def main_image_path + fetch_attr('main_image_path') + end + + def sun_requirements + fetch_attr('sun_requirements') + end + + def growing_degree_days + fetch_attr('growing_degree_days') + end + + def processing_pictures + fetch_attr('processing_pictures') + end + end + + def fetch_attr(key) + return if openfarm_data.blank? + + openfarm_data.fetch('attributes', {}).fetch(key, nil) + end +end diff --git a/app/models/concerns/photo_capable.rb b/app/models/concerns/photo_capable.rb index dfddef42c..9cb440bd7 100644 --- a/app/models/concerns/photo_capable.rb +++ b/app/models/concerns/photo_capable.rb @@ -2,7 +2,7 @@ module PhotoCapable extend ActiveSupport::Concern included do - has_many :photo_associations, as: :photographable, dependent: :destroy, inverse_of: :photographable + has_many :photo_associations, as: :photographable, dependent: :delete_all, inverse_of: :photographable has_many :photos, through: :photo_associations, as: :photographable scope :has_photos, -> { includes(:photos).where.not(photos: { id: nil }) } diff --git a/app/models/concerns/predict_planting.rb b/app/models/concerns/predict_planting.rb index 8b2961f64..e02fe5e5b 100644 --- a/app/models/concerns/predict_planting.rb +++ b/app/models/concerns/predict_planting.rb @@ -41,7 +41,6 @@ module PredictPlanting (Time.zone.today - planted_at).to_i if planted_at.present? end - # progress def percentage_grown if finished? 100 diff --git a/app/models/crop.rb b/app/models/crop.rb index e0166744d..d0283a2b6 100644 --- a/app/models/crop.rb +++ b/app/models/crop.rb @@ -1,28 +1,29 @@ class Crop < ApplicationRecord extend FriendlyId include PhotoCapable + include OpenFarmData friendly_id :name, use: %i(slugged finders) - ## - ## Triggers - before_destroy { |crop| crop.posts.clear } - ## ## Relationships - has_many :scientific_names, dependent: :destroy - accepts_nested_attributes_for :scientific_names, allow_destroy: true, reject_if: :all_blank - has_many :alternate_names, dependent: :destroy - has_many :plantings, dependent: :destroy - has_many :seeds, dependent: :destroy - has_many :harvests, dependent: :destroy - has_many :photo_associations, dependent: :destroy - has_many :photos, through: :photo_associations - has_many :plant_parts, -> { joins_members.distinct.order("plant_parts.name") }, through: :harvests belongs_to :creator, class_name: 'Member', optional: true, inverse_of: :created_crops belongs_to :requester, class_name: 'Member', optional: true, inverse_of: :requested_crops belongs_to :parent, class_name: 'Crop', optional: true, inverse_of: :varieties + has_many :scientific_names, dependent: :delete_all + has_many :alternate_names, dependent: :delete_all + has_many :plantings, dependent: :destroy + has_many :seeds, dependent: :destroy + has_many :harvests, dependent: :destroy + has_many :photo_associations, dependent: :delete_all + has_many :photos, through: :photo_associations + has_many :plant_parts, -> { joins_members.distinct.order("plant_parts.name") }, through: :harvests has_many :varieties, class_name: 'Crop', foreign_key: 'parent_id', dependent: :nullify, inverse_of: :parent - has_and_belongs_to_many :posts # rubocop:disable Rails/HasAndBelongsToMany + has_many :crop_companions, foreign_key: :crop_a_id, dependent: :delete_all, inverse_of: :crop_a + has_many :companions, through: :crop_companions, source: :crop_b, class_name: 'Crop' + has_many :crop_posts, dependent: :delete_all + has_many :posts, through: :crop_posts, dependent: :delete_all + + accepts_nested_attributes_for :scientific_names, allow_destroy: true, reject_if: :all_blank ## ## Scopes @@ -43,8 +44,6 @@ class Crop < ApplicationRecord ## Validations # Reasons are only necessary when rejecting validates :reason_for_rejection, presence: true, if: :rejected? - ## This validation addresses a race condition - validate :approval_status_cannot_be_changed_again validate :must_be_rejected_if_rejected_reasons_present validate :must_have_meaningful_reason_for_rejection ## Wikipedia urls are only necessary when approving a crop @@ -97,6 +96,10 @@ class Crop < ApplicationRecord .count("harvests.id") end + def perennial? + perennial == true + end + def annual? perennial == false end @@ -165,6 +168,10 @@ class Crop < ApplicationRecord } end + def fetch_from_openfarm! + OpenfarmService.new.update_crop(self) + end + private def count_uses_of_property(col_name) @@ -175,14 +182,6 @@ class Crop < ApplicationRecord .count end - # Custom validations - def approval_status_cannot_be_changed_again - previous = previous_changes.include?(:approval_status) ? previous_changes.approval_status : {} - return unless previous.include?(:rejected) || previous.include?(:approved) - - errors.add(:approval_status, "has already been set to #{approval_status}") - end - def must_be_rejected_if_rejected_reasons_present return if rejected? return unless reason_for_rejection.present? || rejection_notes.present? diff --git a/app/models/crop_companion.rb b/app/models/crop_companion.rb new file mode 100644 index 000000000..55d7c27a1 --- /dev/null +++ b/app/models/crop_companion.rb @@ -0,0 +1,4 @@ +class CropCompanion < ApplicationRecord + belongs_to :crop_a, class_name: :Crop + belongs_to :crop_b, class_name: :Crop +end diff --git a/app/models/crop_post.rb b/app/models/crop_post.rb new file mode 100644 index 000000000..5c520e3b6 --- /dev/null +++ b/app/models/crop_post.rb @@ -0,0 +1,4 @@ +class CropPost < ApplicationRecord + belongs_to :crop + belongs_to :post +end diff --git a/app/models/harvest.rb b/app/models/harvest.rb index 8a78ad4fc..36f61965f 100644 --- a/app/models/harvest.rb +++ b/app/models/harvest.rb @@ -111,15 +111,11 @@ class Harvest < ApplicationRecord end def unit_to_human - return "" unless quantity + return "" unless quantity && unit + return 'individual' if unit == 'individual' + return "#{unit} of" if quantity == 1 - if unit == 'individual' - 'individual' - elsif quantity == 1 - "#{unit} of" - else - "#{unit.pluralize} of" - end + "#{unit.pluralize} of" end def weight_to_human diff --git a/app/models/member.rb b/app/models/member.rb index e809c5370..cc86c8b14 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -50,6 +50,7 @@ class Member < ApplicationRecord scope :recently_joined, -> { reorder(confirmed_at: :desc) } scope :interesting, -> { confirmed.located.recently_signed_in.has_plantings } scope :has_plantings, -> { joins(:plantings).group("members.id") } + scope :wants_reminders, -> { where(send_planting_reminder: true) } # Include default devise modules. Others available are: # :token_authenticatable, :confirmable, diff --git a/app/models/photo.rb b/app/models/photo.rb index 8ed9c2f57..1490c33dc 100644 --- a/app/models/photo.rb +++ b/app/models/photo.rb @@ -2,11 +2,14 @@ class Photo < ApplicationRecord include Likeable include Ownable - PHOTO_CAPABLE = %w(Garden Planting Harvest Seed Post).freeze + PHOTO_CAPABLE = %w(Garden Planting Harvest Seed Post Crop).freeze - has_many :photo_associations, foreign_key: :photo_id, dependent: :destroy, inverse_of: :photo + has_many :photo_associations, foreign_key: :photo_id, dependent: :delete_all, inverse_of: :photo has_many :crops, through: :photo_associations + validates :fullsize_url, url: true + validates :thumbnail_url, url: true + # creates a relationship for each assignee type PHOTO_CAPABLE.each do |type| has_many type.downcase.pluralize.to_s.to_sym, @@ -24,7 +27,7 @@ class Photo < ApplicationRecord # for easier stubbing and testing. def flickr_metadata flickr = owner.flickr - info = flickr.photos.getInfo(photo_id: flickr_photo_id) + info = flickr.photos.getInfo(photo_id: source_id) licenses = flickr.photos.licenses.getInfo license = licenses.find { |l| l.id == info.license } { @@ -63,4 +66,8 @@ class Photo < ApplicationRecord def to_s "#{title} by #{owner.login_name}" end + + def flickr_photo_id + source_id if source == 'flickr' + end end diff --git a/app/models/photo_association.rb b/app/models/photo_association.rb index 24eb79e80..89e495b0f 100644 --- a/app/models/photo_association.rb +++ b/app/models/photo_association.rb @@ -18,12 +18,18 @@ class PhotoAssociation < ApplicationRecord end def set_crop - self.crop_id = photographable.crop_id if %w(Planting Seed Harvest).include?(photographable_type) + if %w(Planting Seed Harvest).include?(photographable_type) + self.crop_id = photographable.crop_id + elsif photographable_type == 'Crop' + self.crop_id = photographable_id + end end private def photo_and_item_have_same_owner + return unless photographable_type != 'Crop' + errors.add(:photo, "must have same owner as item it links to") unless photographable.owner_id == photo.owner_id end end diff --git a/app/models/planting.rb b/app/models/planting.rb index 0555e4ac5..868f36735 100644 --- a/app/models/planting.rb +++ b/app/models/planting.rb @@ -33,6 +33,8 @@ class Planting < ApplicationRecord ## ## Scopes scope :active, -> { where('finished <> true').where('finished_at IS NULL OR finished_at < ?', Time.zone.now) } + scope :annual, -> { joins(:crop).where(crops: { perennial: false }) } + scope :perennial, -> { joins(:crop).where(crops: { perennial: true }) } scope :interesting, -> { has_photos.one_per_owner.order(planted_at: :desc) } scope :recent, -> { order(created_at: :desc) } scope :one_per_owner, lambda { diff --git a/app/models/post.rb b/app/models/post.rb index 685ffee19..b2a8b88fd 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -9,14 +9,12 @@ class Post < ApplicationRecord belongs_to :author, class_name: 'Member', inverse_of: :posts belongs_to :forum, optional: true has_many :comments, dependent: :destroy - has_and_belongs_to_many :crops # rubocop:disable Rails/HasAndBelongsToMany - # also has_many notifications, but kinda meaningless to get at them - # from this direction, so we won't set up an association for now. + has_many :crop_posts, dependent: :delete_all + has_many :crops, through: :crop_posts # # Triggers - before_destroy { |post| post.crops.clear } - after_save :update_crops_posts_association + after_save :update_crop_posts_association after_create :send_notification default_scope { joins(:author).merge(Member.kept) } # Ensures the owner still exists @@ -59,12 +57,12 @@ class Post < ApplicationRecord private - def update_crops_posts_association - crops.destroy_all + def update_crop_posts_association + crops.clear # look for crops mentioned in the post. eg. [tomato](crop) body.scan(Haml::Filters::GrowstuffMarkdown::CROP_REGEX) do |_m| - # find crop case-insensitively - crop = Crop.case_insensitive_name(Regexp.last_match(1)).first + crop_name = Regexp.last_match(1) + crop = Crop.case_insensitive_name(crop_name).first # create association crops << crop if crop && !crops.include?(crop) end diff --git a/app/models/role.rb b/app/models/role.rb index 3606641ea..ea17cfb91 100644 --- a/app/models/role.rb +++ b/app/models/role.rb @@ -1,6 +1,7 @@ class Role < ApplicationRecord extend FriendlyId friendly_id :name, use: %i(slugged finders) + validates :name, uniqueness: true, presence: true has_and_belongs_to_many :members # rubocop:disable Rails/HasAndBelongsToMany diff --git a/app/models/seed.rb b/app/models/seed.rb index 8e0544b52..32c0ef6a5 100644 --- a/app/models/seed.rb +++ b/app/models/seed.rb @@ -53,7 +53,7 @@ class Seed < ApplicationRecord scope :interesting, -> { tradable.has_location } scope :has_location, -> { joins(:owner).where.not("members.location": nil) } scope :recent, -> { order(created_at: :desc) } - scope :active, -> { where('finished_at < ?', Time.zone.now) } + scope :active, -> { where('finished <> true').where('finished_at IS NULL OR finished_at < ?', Time.zone.now) } def tradable? tradable_to != 'nowhere' diff --git a/app/services/openfarm_service.rb b/app/services/openfarm_service.rb new file mode 100644 index 000000000..b9cd243c6 --- /dev/null +++ b/app/services/openfarm_service.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +BASE = 'https://openfarm.cc/api/v1/' +# BASE = 'http://127.0.0.1:3000/api/v1/' + +class OpenfarmService + def initialize + @cropbot = Member.find_by(login_name: 'cropbot') + end + + def import! + Crop.all.order(updated_at: :desc).each do |crop| + Rails.logger.debug("#{crop.id}, #{crop.name}") + update_crop(crop) if crop.valid? + end + end + + def update_crop(crop) + openfarm_record = fetch(crop.name) + if openfarm_record.present? && openfarm_record.is_a?(String) + Rails.logger.info(openfarm_record) + elsif openfarm_record.present? && openfarm_record.fetch('data', false) + crop.update! openfarm_data: openfarm_record.fetch('data', false) + save_companions(crop, openfarm_record) + save_photos(crop) + else + Rails.logger.debug "\tcrop not found on Open Farm" + crop.update!(openfarm_data: false) + end + end + + def save_companions(crop, openfarm_record) + companions = openfarm_record.fetch('data').fetch('relationships').fetch('companions').fetch('data') + crops = openfarm_record.fetch('included', []).select { |rec| rec["type"] == 'crops' } + CropCompanion.transaction do + companions.each do |com| + companion_crop_hash = crops.detect { |c| c.fetch('id') == com.fetch('id') } + companion_crop_name = companion_crop_hash.fetch('attributes').fetch('name').downcase + companion_crop = Crop.where('lower(name) = ?', companion_crop_name).first + companion_crop = Crop.create!(name: companion_crop_name, requester: @cropbot, approval_status: "pending") if companion_crop.nil? + crop.companions << companion_crop unless crop.companions.where(id: companion_crop.id).any? + end + end + end + + def save_photos(crop) + pictures = fetch_pictures(crop.name) + pictures.each do |picture| + data = picture.fetch('attributes') + Rails.logger.debug(data) + next unless data.fetch('image_url').start_with? 'http' + next if Photo.find_by(source_id: picture.fetch('id'), source: 'openfarm') + + photo = Photo.new( + source_id: picture.fetch('id'), + source: 'openfarm', + owner: @cropbot, + thumbnail_url: data.fetch('thumbnail_url'), + fullsize_url: data.fetch('image_url'), + title: 'Open Farm photo', + license_name: 'No rights reserved', + link_url: "https://openfarm.cc/en/crops/#{name_to_slug(crop.name)}" + ) + if photo.valid? + Photo.transaction do + photo.save + PhotoAssociation.find_or_create_by! photo: photo, photographable: crop + end + Rails.logger.debug "\t saved photo #{photo.id} #{photo.source_id}" + else + Rails.logger.warn "Photo not valid" + end + end + end + + def fetch(name) + conn.get("crops/#{name_to_slug(name)}.json").body + rescue NoMethodError + Rails.logger.debug "error fetching crop" + Rails.logger.debug "BODY: " + Rails.logger.debug body + end + + def name_to_slug(name) + CGI.escape(name.gsub(' ', '-').downcase) + end + + def fetch_all(page) + conn.get("crops.json?page=#{page}").body.fetch('data', {}) + end + + def fetch_pictures(name) + body = conn.get("crops/#{name_to_slug(name)}/pictures.json").body + body.fetch('data', false) + rescue StandardError + Rails.logger.debug "Error fetching photos" + Rails.logger.debug [] + end + + private + + def conn + Faraday.new BASE do |conn| + conn.response :json, content_type: /\bjson$/ + conn.adapter Faraday.default_adapter + end + end +end diff --git a/app/views/admin/index.html.haml b/app/views/admin/index.html.haml index a0a458c56..9a60bb324 100644 --- a/app/views/admin/index.html.haml +++ b/app/views/admin/index.html.haml @@ -14,7 +14,7 @@ Site admin .card-body %nav.nav#site_admin - %li= link_to "Roles", roles_path, class: 'nav-link' + %li= link_to "Roles", admin_roles_path, class: 'nav-link' %li= link_to "Forums", forums_path, class: 'nav-link' %li= link_to "CMS", comfy_admin_cms_path, class: 'nav-link' %li= link_to t('.garden_types'), garden_types_path, class: 'nav-link' diff --git a/app/views/admin/members/_form.html.haml b/app/views/admin/members/_form.html.haml new file mode 100644 index 000000000..24c369576 --- /dev/null +++ b/app/views/admin/members/_form.html.haml @@ -0,0 +1,14 @@ +%section.card.form-card + = bootstrap_form_for [:admin, @member] do |f| + - if @member.errors.any? + #error_explanation + %h2 + = pluralize(@member.errors.size, "error") + prohibited this role from being saved: + %ul + - @member.errors.full_messages.each do |msg| + %li= msg + .card-body + = f.collection_check_boxes(:role_ids, Role.all, :id, :name) + .card-footer + = f.submit 'Save', class: 'btn btn-success' diff --git a/app/views/admin/members/edit.html.haml b/app/views/admin/members/edit.html.haml new file mode 100644 index 000000000..944ef00c6 --- /dev/null +++ b/app/views/admin/members/edit.html.haml @@ -0,0 +1,3 @@ +.row + .col= render 'members/member', member: @member + .col= render 'form' diff --git a/app/views/admin/members/index.html.haml b/app/views/admin/members/index.html.haml index 0a8d440ad..4d276fc49 100644 --- a/app/views/admin/members/index.html.haml +++ b/app/views/admin/members/index.html.haml @@ -1,3 +1,7 @@ +- content_for :breadcrumbs do + %li.breadcrumb-item= link_to 'Admin', admin_path + %li.breadcrumb-item.active= link_to 'Members', admin_members_path + .row .col-md-6 = bootstrap_form_tag url: admin_members_path, method: :get, layout: :horizontal do |f| @@ -7,19 +11,22 @@ = f.text_field(:q, 'aria-label': 'search members', placeholder: 'search members', hide_label: true) = f.submit 'search', class: 'btn btn-secondary' - %table.table.table-striped %thead %tr %th{scope: "col"} # %th{scope: "col"} Login Name - %th{scope: "col"} Email - @members.each do |member| %tr %td= render 'members/tiny', member: member %td= member.login_name - %td= member.email %td + %ul + - member.roles.pluck(:name).each do |r| + %li= r + %td + = link_to edit_admin_member_path(member), class: 'btn' do + = edit_icon = link_to admin_member_path(member), method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-light text-danger' do = icon 'fas', 'ban' Ban member diff --git a/app/views/admin/roles/_form.html.haml b/app/views/admin/roles/_form.html.haml new file mode 100644 index 000000000..6188207dd --- /dev/null +++ b/app/views/admin/roles/_form.html.haml @@ -0,0 +1,16 @@ +.form-page + %section.card.form-card + = bootstrap_form_for [:admin, @role] do |f| + - if @role.errors.any? + #error_explanation + %h2 + = pluralize(@role.errors.size, "error") + prohibited this role from being saved: + %ul + - @role.errors.full_messages.each do |msg| + %li= msg + .card-body + = f.text_field :name + = f.text_area :description + .card-footer + = f.submit 'Save', class: 'btn btn-success' diff --git a/app/views/admin/roles/edit.html.haml b/app/views/admin/roles/edit.html.haml new file mode 100644 index 000000000..e98da7412 --- /dev/null +++ b/app/views/admin/roles/edit.html.haml @@ -0,0 +1,8 @@ +- content_for :title, "Editing role" +- content_for :breadcrumbs do + %li.breadcrumb-item= link_to 'Admin', admin_path + %li.breadcrumb-item= link_to 'Roles', admin_roles_path + %li.breadcrumb-item.active= link_to 'Edit', edit_admin_role_path(@role) += render 'form' + += link_to 'Back', admin_roles_path diff --git a/app/views/admin/roles/index.html.haml b/app/views/admin/roles/index.html.haml new file mode 100644 index 000000000..eced7a10d --- /dev/null +++ b/app/views/admin/roles/index.html.haml @@ -0,0 +1,30 @@ +- content_for :title, "Listing roles" + +- content_for :breadcrumbs do + %li.breadcrumb-item= link_to 'Admin', admin_path + %li.breadcrumb-item.active= link_to 'Roles', admin_roles_path + +- if can? :create, Role + %p= link_to 'New Role', new_admin_role_path, class: 'btn btn-primary' + +%table.table.table-striped + %thead + %tr + %th Name + %th Description + %th + %th + + - @roles.each do |role| + %tr + %td= role.name + %td= role.description + %td + - if can? :edit, role + = link_to edit_admin_role_path(role), class: 'btn btn-default btn-xs' do + = edit_icon + = t('.edit') + - if can?(:destroy, role) && ! role.members.any? + = link_to admin_role_path(role), method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-default btn-xs text-danger' do + = delete_icon + = t('.delete') diff --git a/app/views/admin/roles/new.html.haml b/app/views/admin/roles/new.html.haml new file mode 100644 index 000000000..1b0e660aa --- /dev/null +++ b/app/views/admin/roles/new.html.haml @@ -0,0 +1,11 @@ +- content_for :title, "New role" + + +- content_for :breadcrumbs do + %li.breadcrumb-item= link_to 'Admin', admin_path + %li.breadcrumb-item= link_to 'Roles', admin_roles_path + %li.breadcrumb-item.active= link_to 'New', new_admin_role_path + += render 'form' + += link_to 'Back', admin_roles_path diff --git a/app/views/conversations/index.haml b/app/views/conversations/index.haml index 81f38c125..550ecf9b7 100644 --- a/app/views/conversations/index.haml +++ b/app/views/conversations/index.haml @@ -3,55 +3,58 @@ %li.breadcrumb-item= link_to 'Conversations', conversations_path %li.breadcrumb-item.active= link_to @box, conversations_path(box: @box) -.row - .col-md-2 - .col-md-10 - %h1= @box - - unless @conversations.empty? - = will_paginate @conversations -.row - .col-md-2.list-group - - @boxes.each do |box_name, counts| - = link_to conversations_path(box: box_name), class: "nav-link list-group-item d-flex justify-content-between #{box_name == @box ? 'active' : ''} #{box_name}" do - - if counts['unread'].positive? - %span.badge.badge-info=counts['unread'] - - else - %span - %span - = icon 'fas', box_name - = box_name += form_tag destroy_multiple_conversations_path, method: :delete do + .row.py-4 + .col-2.offset-md-2 + %h1= @box + .col-8 + = button_tag(type: 'submit', class: 'btn btn-default') do + = icon 'fas', 'trash-alt' + = 'Delete Selected' - .col-md-10 - .list-group - - @conversations.each do |conversation| - .list-group-item - .row - .col-md-1 - - if conversation.receipts_for(current_member).last.is_unread? - %h1= icon 'far', 'envelope' - - else - %h1.text-muted= icon 'far', 'envelope-open' - .col-md-10 - .text-right.float-right - - conversation.recipients.each do |member| - - if member != current_member - = render 'members/tiny', member: member - = link_to conversation_path(conversation) do - .conversation - %h5.mb-2.h5 - - if conversation.receipts_for(current_member).last.is_unread? - %strong= conversation.subject - - else - = conversation.subject - %small - #{time_ago_in_words conversation.messages.last.created_at} ago - %span.text-muted= conversation.messages.last.created_at - = truncate(strip_tags(conversation.messages.last.body), length: 150, separator: ' ', omission: '... ') - .col-md-1 - - if @box == 'trash' - = link_to conversation_path(conversation, box: @box), method: :put, class: 'restore' do - = icon 'fas', 'trash-restore' - - else - = link_to delete_icon, conversation_path(conversation, box: @box), method: :delete, class: 'delete text-danger' - - unless @conversations.empty? - = will_paginate @conversations + .row + .col-md-2.py-4 + - @boxes.each do |box_name, counts| + = link_to conversations_path(box: box_name), class: "nav-link list-group-item d-flex justify-content-between #{box_name == @box ? 'active' : ''} #{box_name}" do + - if counts['unread'].positive? + %span.badge.badge-info=counts['unread'] + - else + %span + %span + = icon 'fas', box_name + = box_name + + .col-md-10 + .list-group + - @conversations.each do |conversation| + .list-group-item + .row + .col-md-1 + - if conversation.receipts_for(current_member).last.is_unread? + %h1= icon 'far', 'envelope' + - else + %h1.text-muted= icon 'far', 'envelope-open' + .col-md-10 + .text-right.float-right + - conversation.recipients.each do |member| + - if member != current_member + = render 'members/tiny', member: member + = link_to conversation_path(conversation) do + .conversation + %h5.mb-2.h5 + - if conversation.receipts_for(current_member).last.is_unread? + %strong= conversation.subject + - else + = conversation.subject + %small + #{time_ago_in_words conversation.messages.last.created_at} ago + %span.text-muted= conversation.messages.last.created_at + = truncate(strip_tags(conversation.messages.last.body), length: 150, separator: ' ', omission: '... ') + .col-md-1 + - if @box == 'trash' + = link_to conversation_path(conversation, box: @box), method: :put, class: 'restore' do + = icon 'fas', 'trash-restore' + - else + = check_box_tag 'conversation_ids[]', conversation.id, false, class: 'selectable' + - unless @conversations.empty? + = will_paginate @conversations diff --git a/app/views/crops/_actions.html.haml b/app/views/crops/_actions.html.haml index 60d337c77..835edea1c 100644 --- a/app/views/crops/_actions.html.haml +++ b/app/views/crops/_actions.html.haml @@ -1,16 +1,22 @@ -- if @crop.approved? && signed_in? - .crop-actions - - if can? :create, Planting - = link_to new_planting_path(crop_id: crop.id), class: 'btn btn-sm' do - = planting_icon - = t('buttons.plant_crop', crop_name: crop.name) - - - if can? :create, Harvest - = link_to new_harvest_path(crop_id: crop.id), class: 'btn btn-sm' do - = harvest_icon - = t('buttons.harvest_crop', crop_name: crop.name) - - - if can? :create, Seed - = link_to new_seed_path(crop_id: crop.id), class: 'btn btn-sm' do - = seed_icon - = t('buttons.add_seed_to_stash', crop_name: crop.name) +- if crop.approved? && signed_in? + %ul.nav.crop-actions + %li.nav-item.dropdown.btn.btn-sm + %a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", role: "button", href: '#'} Add to garden + .dropdown-menu.dropdown-secondary + - current_member.gardens.active.each do |garden| + = link_to plantings_path(planting: {crop_id: crop.id, garden_id: garden.id}), method: :post, class: 'dropdown-item' do + = garden.name + .dropdown-divider + = link_to 'add new garden', new_garden_path, class: 'dropdown-item' + %li.nav-item.dropdown.btn.btn-sm + %a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", role: "button", href: '#'} Record harvest + .dropdown-menu.dropdown-secondary + - PlantPart.all.each do |plant_part| + = link_to harvests_path(harvest: {crop_id: crop.id, plant_part_id: plant_part.id}), method: :post, class: 'dropdown-item' do + = plant_part.name + %li.nav-item.dropdown.btn.btn-sm + %a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", role: "button", href: '#'}=t('buttons.save_seeds', crop_name: crop.name) + .dropdown-menu.dropdown-secondary + - Seed::TRADABLE_TO_VALUES.each do |trade| + = link_to seeds_path(seed: {crop_id: crop.id, tradable_to: trade}), method: :post, class: 'dropdown-item' do + Will trade: #{trade} diff --git a/app/views/crops/_alternate_names.html.haml b/app/views/crops/_alternate_names.html.haml index 5d75b81f3..04c4314fc 100644 --- a/app/views/crops/_alternate_names.html.haml +++ b/app/views/crops/_alternate_names.html.haml @@ -1,26 +1,23 @@ .alternate_names %h4 Alternate names - - if crop.alternate_names.empty? - %p None known. - - else - %ul.list-group.list-group-flush - - crop.alternate_names.each do |an| - %li.list-group-item.d-flex.justify-content-between.align-items-center - - if can? :edit, an - .dropdown.planting-actions - %a#crop-actions-altnames.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", :type => "button", :href => '#'} - = an.name - .dropdown-menu.dropdown-menu-xs{"aria-labelledby" => "crop-actions-altnames"} - - if can? :edit, an - = link_to edit_alternate_name_path(an), class: 'dropdown-item' do - = edit_icon - = t('.edit') - - if can? :destroy, an - = link_to an, method: :delete, data: { confirm: 'Are you sure?' }, class: 'dropdown-item' do - = delete_icon - = t('.delete') - - else + - if crop.alternate_names.any? + - crop.alternate_names.each do |an| + - if can? :edit, an + .dropdown.planting-actions + %a#crop-actions-altnames.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", :type => "button", :href => '#'} = an.name + .dropdown-menu.dropdown-menu-xs{"aria-labelledby" => "crop-actions-altnames"} + - if can? :edit, an + = link_to edit_alternate_name_path(an), class: 'dropdown-item' do + = edit_icon + = t('.edit') + - if can? :destroy, an + = link_to an, method: :delete, data: { confirm: 'Are you sure?' }, class: 'dropdown-item' do + = delete_icon + = t('.delete') + - else + .badge= an.name + %p.text-right - if can? :edit, crop diff --git a/app/views/crops/_crop.html.haml b/app/views/crops/_crop.html.haml index 40bd4e508..9c763518d 100644 --- a/app/views/crops/_crop.html.haml +++ b/app/views/crops/_crop.html.haml @@ -1,30 +1,32 @@ .card.card-crop .crop-image - = link_to image_tag(crop_image_path(crop, full_size: true), + = link_to image_tag(crop_image_path(crop), alt: '', class: 'img img-card'), crop - .card-body - %h3.card-title= link_to crop, crop - %p.crop-sci-name= crop.default_scientific_name - - if crop.annual? && crop.median_lifespan.present? - %p.card-text - Median Lifespan - %b= crop.median_lifespan - days - - unless crop.median_days_to_first_harvest.nil? - %p.card-text - First harvest expected - %b= crop.median_days_to_first_harvest - days after planting + %h3.card-title + %strong= link_to crop, crop + = crop.default_scientific_name + .d-flex.justify-content-between + - if crop.annual? && crop.median_lifespan.present? + %p.small + Median Lifespan + %strong= crop.median_lifespan + days + - unless crop.median_days_to_first_harvest.nil? + %p.small + First harvest expected + %strong= crop.median_days_to_first_harvest + days after planting - - if crop.annual? && crop.median_days_to_last_harvest.present? - %p.card-text - Last harvest expected - %b= crop.median_days_to_last_harvest - days after planting - - if member_signed_in? - .card-footer - = crop_plant_button(crop) - = crop_save_seeds_button(crop) + - if crop.annual? && crop.median_days_to_last_harvest.present? + %p.small + Last harvest expected + %strong= crop.median_days_to_last_harvest + days after planting + - if member_signed_in? + .card-footer + .d-flex.justify-content-between + = crop_plant_button(crop) + = crop_save_seeds_button(crop) diff --git a/app/views/crops/_form.html.haml b/app/views/crops/_form.html.haml index 53c329f7c..2ef58f243 100644 --- a/app/views/crops/_form.html.haml +++ b/app/views/crops/_form.html.haml @@ -41,6 +41,8 @@ = f.radio_button(:perennial, true, label: "Perennial") %span.help-block Living more than two years + - unless @crop.approved? + = link_to 'Search wikipedia', "https://en.wikipedia.org/w/index.php?search=#{@crop.name}", target: '_blank' = f.text_field :en_wikipedia_url, id: "en_wikipedia_url", label: 'Wikipedia URL' %span.help-block Link to the crop's page on the English language Wikipedia (required). @@ -54,8 +56,8 @@ -# Everyone (wranglers and requesters) gets to add scientific names %h2 Scientific names - = button_tag "+", id: "add-sci_name-row", type: "button" - = button_tag "-", id: "remove-sci_name-row", type: "button" + = button_tag "+", class: "add-sciname-row", type: "button" + = button_tag "-", class: "remove-sciname-row", type: "button" .form-group#scientific_names - @crop.scientific_names.each.with_index do |sci, index| @@ -66,8 +68,8 @@ = text_field_tag "sci_name[#{index + 1}]", sci.name, id: "sci_name[#{index + 1}]", class: 'form-control' %span.help-block Scientific name of crop. %h2 Alternate names - = button_tag "+", id: "add-alt_name-row", type: "button" - = button_tag "-", id: "remove-alt_name-row", type: "button" + = button_tag "+", class: "add-altname-row", type: "button" + = button_tag "-", class: "remove-altname-row", type: "button" .form-group#alternate_names - @crop.alternate_names.each.with_index do |alt, index| @@ -94,8 +96,8 @@ -# Now, for crop wranglers, let's have approval/rejection at the bottom of the page - if can?(:wrangle, @crop) && @crop.requester - %h2 Approve or reject pending crops - = f.select(:approval_status, @crop.approval_statuses, {}) + -# %h2 Approve or reject pending crops + -# = f.select(:approval_status, @crop.approval_statuses, {}) = f.select(:reason_for_rejection, @crop.reasons_for_rejection, include_blank: true) = f.text_area :rejection_notes, rows: 3 @@ -103,4 +105,9 @@ Please provide additional notes why this crop request was rejected if the above reasons do not apply. .card-footer - .text-right= f.submit 'Save' + .text-right + - if @crop.approved? + = f.submit 'Save' + - else + = f.submit 'Reject', class: 'btn btn-danger', name: 'reject' + = f.submit 'Approve and save', class: 'btn btn-success', name: 'approve' diff --git a/app/views/crops/_grown_for.html.haml b/app/views/crops/_grown_for.html.haml index 0544bdb5f..dccb4821b 100644 --- a/app/views/crops/_grown_for.html.haml +++ b/app/views/crops/_grown_for.html.haml @@ -1,8 +1,6 @@ -%p - %strong Grown for: - - if crop.harvests.empty? - not known. - - else +- if crop.harvests.any? + %p + %strong Grown for: - crop.popular_plant_parts.each do |plant_part, frequency| - id, name = plant_part = link_to name, plant_part_path(id: id) diff --git a/app/views/crops/_hierarchy.html.haml b/app/views/crops/_hierarchy.html.haml index a4b760354..2e8af76c3 100644 --- a/app/views/crops/_hierarchy.html.haml +++ b/app/views/crops/_hierarchy.html.haml @@ -4,8 +4,10 @@ - max = 0 # list all without "show all" toggle button - display_crops.each do |c| %li.crop-hierarchy{ class: (max != 0 && @count >= max) || !c.approved? ? ['hide', 'toggle'] : [] } - = link_to c, c + = render 'crops/tiny', crop: c + - if c.perennial + = perennial_icon - @count += 1 - if c.varieties.present? - - c.varieties.each do |v| + - c.varieties.order(:name).each do |v| = render partial: 'hierarchy', locals: { display_crops: [v], max: max } diff --git a/app/views/crops/_info.haml b/app/views/crops/_info.haml new file mode 100644 index 000000000..2e4266dc6 --- /dev/null +++ b/app/views/crops/_info.haml @@ -0,0 +1,28 @@ +.row + .col-md-9 + %h1.display-3 + = crop_icon(@crop) + %strong= @crop.name.titleize + - unless @crop.approved? + %badge.badge-warning=@crop.approval_status + %small.text-muted= @crop.default_scientific_name + - if @crop.sowing_method.present? + %h2 + How to sow #{@crop.name}: + = @crop.sowing_method + - if @crop.sun_requirements.present? + %p + %strong Sun requirement for #{@crop}: + Plant in #{@crop.sun_requirements} + %p.text-muted + - if !@crop.plantings.empty? + #{@crop.name.titleize} has been planted + = pluralize(@crop.plantings.size, "time") + by #{ENV['GROWSTUFF_SITE_NAME']} members. + - else + Nobody is growing this yet. You could be the first! + - if @crop.description.present? + %p= @crop.description + .col-md-3 + = image_tag crop_image_path(@crop), + class: 'img-responsive shadow rounded crop-hero-photo', alt: 'photo of crop' diff --git a/app/views/crops/_photos.html.haml b/app/views/crops/_photos.html.haml index 71358d541..8fe35a6cf 100644 --- a/app/views/crops/_photos.html.haml +++ b/app/views/crops/_photos.html.haml @@ -1,9 +1,7 @@ - if @crop.photos.size.positive? %h2 #{photo_icon} Photos - - [Planting, Harvest, Seed].each do |model_name| + - [Crop, Planting, Harvest, Seed].each do |model_name| - if photos.by_model(model_name).size.positive? %h3 #{@crop.name.capitalize} #{t("activerecord.models.#{model_name.to_s.downcase}.other")} - = render 'photos/gallery', photos: photos.by_model(model_name).order(likes_count: :desc).limit(8) - + = render 'photos/gallery', photos: photos.by_model(model_name).order(likes_count: :desc).limit(5) = link_to 'more photos »', crop_photos_path(@crop), class: 'btn' - %hr/ diff --git a/app/views/crops/_planting_advice.html.haml b/app/views/crops/_planting_advice.html.haml index 4a444794a..a554a7058 100644 --- a/app/views/crops/_planting_advice.html.haml +++ b/app/views/crops/_planting_advice.html.haml @@ -1,15 +1,6 @@ -%p - %strong Plant from: - - if crop.planted_from.empty? - not known. - - else - - planted_from = crop.planted_from.sort_by { |_, freq| freq }.reverse - = planted_from.map { |s, freq| "#{s} (#{freq})" }.join(", ") +- if crop.guides_count.present? && crop.guides_count.positive? + %p + There are + = link_to "https://openfarm.cc/en/crops/#{CGI.escape @crop.name.gsub(' ', '-').downcase}" do + #{crop.guides_count} growing guides on Open Farm -%p - %strong Plant in: - - if crop.sunniness.empty? - not known. - - else - - sunniness = crop.sunniness.sort_by { |_, freq| freq }.reverse - = sunniness.map { |s, freq| "#{s} (#{freq})" }.join(", ") diff --git a/app/views/crops/_posts.html.haml b/app/views/crops/_posts.html.haml index 2012f642a..1ce1bc7ff 100644 --- a/app/views/crops/_posts.html.haml +++ b/app/views/crops/_posts.html.haml @@ -11,13 +11,12 @@ = render partial: "shared/signin_signup", locals: { to: "post your tips and experiences growing #{crop.name.pluralize}" } - else - .pagination - = page_entries_info @posts - = render 'layouts/pagination', collection: @posts - - @posts.each do |post| - = render 'posts/preview', post: post - .pagination - = page_entries_info @posts - = render 'layouts/pagination', collection: @posts + = will_paginate @posts + + .index-cards + - @posts.each do |post| + = render 'posts/preview', post: post + + = will_paginate @posts diff --git a/app/views/crops/_predictions.html.haml b/app/views/crops/_predictions.html.haml index 82df172df..5d58ad2fb 100644 --- a/app/views/crops/_predictions.html.haml +++ b/app/views/crops/_predictions.html.haml @@ -1,49 +1,56 @@ -%h3 +%h2 = icon 'fas', 'magic' Predictions -.card-deck.mx-auto.predictions +.index-cards.facts - unless crop.perennial.nil? - .col - .card.predictions-card - .card-body.text-center - %h3 - = link_to 'https://en.wikipedia.org/wiki/Annual_vs._perennial_plant_evolution' do - - if crop.perennial == true - Perennial - - elsif crop.perennial == false - Annual + .card.fact-card + .card-body.text-center + %h3 + = link_to 'https://en.wikipedia.org/wiki/Annual_vs._perennial_plant_evolution', class: 'crop-longevity' do + - if crop.perennial? + Perennial + - elsif crop.annual? + Annual + %strong.crop-longevity + %i.far.fa-calendar - %p.display-1 - %i.far.fa-calendar - - - if crop.perennial == true - %small living more than two years - - elsif crop.perennial == false - %small living and reproducing in a single year or less + - if crop.perennial == true + %small living more than two years + - elsif crop.perennial == false + %small living and reproducing in a single year or less + - if can? :wrangle, @crop + %small.edit-link + = bootstrap_form_for(@crop) do |f| + = f.hidden_field :perennial, value: crop.annual? + = f.submit :toggle - if crop.annual? && crop.median_lifespan.present? - .col - .card.predictions-card - .card-body.text-center - %h3 Median lifespan - %p.display-1= crop.median_lifespan - %i.fas.fa-sun-o.fa-5x.pt-3.amber-text - %span days - + .card.fact-card + .card-body.text-center + %h3 Median lifespan + %strong= in_weeks(crop.median_lifespan) + %i.fas.fa-sun-o.fa-5x.pt-3.amber-text + %span #{'week'.pluralize(in_weeks(crop.median_lifespan))} - if crop.median_days_to_first_harvest.present? - .col - .card.predictions-card - .card-body.text-center - %h3 First harvest expected - %p.display-1= crop.median_days_to_first_harvest - %span days after planting - + = render 'layouts/fact_card', + title: 'First harvest expected', + value: in_weeks(crop.median_days_to_first_harvest), + description: "#{'week'.pluralize(in_weeks(crop.median_days_to_first_harvest))} after planting" - if crop.annual? && crop.median_days_to_last_harvest.present? - .col - .card.predictions-card - .card-body.text-center - %h3 Last harvest expected - %p.display-1= crop.median_days_to_last_harvest - %span days after planting - + = render 'layouts/fact_card', + title: "Last harvest expected", + value: in_weeks(crop.median_days_to_last_harvest), + description: "#{'week'.pluralize(in_weeks(crop.median_days_to_last_harvest))} after planting" + - if crop.height.present? + = render 'layouts/fact_card', + title: 'Height', value: "#{crop.height}cm", description: nil + - if crop.spread.present? + = render 'layouts/fact_card', + title: 'Spread', value: "#{crop.spread}cm", description: nil + - if crop.row_spacing.present? + = render 'layouts/fact_card', + title: 'Row Spacing', value: "#{crop.row_spacing}cm", description: nil + - if crop.growing_degree_days.present? + = render 'layouts/fact_card', + title: 'Growing Degree Days', value: crop.growing_degree_days, description: nil diff --git a/app/views/crops/_scientific_names.html.haml b/app/views/crops/_scientific_names.html.haml index 66036e198..bf6ace250 100644 --- a/app/views/crops/_scientific_names.html.haml +++ b/app/views/crops/_scientific_names.html.haml @@ -3,22 +3,20 @@ - if crop.scientific_names.empty? %p None known. - else - %ul.list-group.list-group-flush - - crop.scientific_names.each do |sn| - %li.list-group-item.d-flex.justify-content-between.align-items-center - - if can? :edit, sn - .dropdown.planting-actions - %a#planting-actions-scinames.dropdown-toggle.card-link{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", :type => "button", :href => '#'}= sn.name - .dropdown-menu.dropdown-menu-xs{"aria-labelledby" => "planting-actions-button"} - = link_to edit_scientific_name_path(sn), class: 'dropdown-item' do - = edit_icon - = t('.edit') - .dropdown-divider - = link_to sn, method: :delete, data: { confirm: 'Are you sure?' }, class: 'dropdown-item text-danger' do - = delete_icon - = t('.delete') - - else - = sn.name + - crop.scientific_names.each do |sn| + - if can? :edit, sn + .dropdown.planting-actions + %a#planting-actions-scinames.dropdown-toggle.card-link{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", :type => "button", :href => '#'}= sn.name + .dropdown-menu.dropdown-menu-xs{"aria-labelledby" => "planting-actions-button"} + = link_to edit_scientific_name_path(sn), class: 'dropdown-item' do + = edit_icon + = t('.edit') + .dropdown-divider + = link_to sn, method: :delete, data: { confirm: 'Are you sure?' }, class: 'dropdown-item text-danger' do + = delete_icon + = t('.delete') + - else + .badge= sn.name %p.text-right - if can? :edit, crop diff --git a/app/views/crops/_thumbnail.html.haml b/app/views/crops/_thumbnail.html.haml index 3b1988283..39559e29c 100644 --- a/app/views/crops/_thumbnail.html.haml +++ b/app/views/crops/_thumbnail.html.haml @@ -1,10 +1,11 @@ -.card.card-crop - .crop-image - = link_to image_tag(crop_image_path(crop, full_size: true), - alt: crop.name, - class: 'img img-card'), - crop +.card.crop-thumbnail + = link_to image_tag(crop_image_path(crop), + alt: crop.name, + class: 'img img-card'), + crop - .card-body - %h3.text-center.crop-name= link_to crop, crop - %p.small.crop-sci-name.text-center= crop.default_scientific_name + .text + %h3.crop-name= link_to crop, crop + %h5.crop-sci-name + + = crop.default_scientific_name diff --git a/app/views/crops/_tiny.html.haml b/app/views/crops/_tiny.html.haml new file mode 100644 index 000000000..512379059 --- /dev/null +++ b/app/views/crops/_tiny.html.haml @@ -0,0 +1,7 @@ +- if crop.approved? || can?(:wrangle, Crop) + = link_to crop do + %span.chip.crop-chip + = crop_icon(crop) + = crop.name + - unless crop.approved? + %badge.badge-warning= crop.approval_status diff --git a/app/views/crops/_varieties.html.haml b/app/views/crops/_varieties.html.haml index 2b3407388..3f73ed612 100644 --- a/app/views/crops/_varieties.html.haml +++ b/app/views/crops/_varieties.html.haml @@ -1,5 +1,4 @@ -- if crop.varieties.size.positive? - %h3 Varieties - .row - - crop.varieties.order(:name).each do |v| - .col-md-2.six-across= render 'crops/thumbnail', crop: v \ No newline at end of file +- if crop.varieties.any? + - crop.varieties.order(:name).each do |v| + = render 'crops/thumbnail', crop: v + = render 'crops/varieties', crop: v diff --git a/app/views/crops/_wrangle.html.haml b/app/views/crops/_wrangle.html.haml index 7cedc4ca3..6c93c734d 100644 --- a/app/views/crops/_wrangle.html.haml +++ b/app/views/crops/_wrangle.html.haml @@ -1,6 +1,5 @@ - if can?(:edit, crop) || can?(:destroy, crop) .alert.alert-success{role: "alert"} - %h4 Crop wrangling %p You are a %strong CROP WRANGLER @@ -11,6 +10,10 @@ = edit_icon = t('.edit') + = link_to crop_openfarm_path(crop), method: :post, class: 'dropdown-item' do + = icon 'far', 'update' + Fetch data from OpenFarm + - if can? :destroy, crop .dropdown-divider = delete_button(crop, classes: 'dropdown-item text-danger') diff --git a/app/views/crops/index.html.haml b/app/views/crops/index.html.haml index e0eb151e1..7fc088750 100644 --- a/app/views/crops/index.html.haml +++ b/app/views/crops/index.html.haml @@ -9,30 +9,23 @@ - content_for :breadcrumbs do %li.breadcrumb-item.active= link_to 'Crops', crops_path -%h1=t('.title') -%p - #{ENV['GROWSTUFF_SITE_NAME']} tracks who's growing what, where. - View any crop page to see which of our members have planted it and find - information on how to grow it yourself. - - = bootstrap_form_tag(url: crops_path, method: :get, layout: :inline) do |f| - = f.select "sort", - options_for_select({ "popularity": 'popular', - "alphabetically": 'alpha' }, - @sort || 'popular'), - label: 'Sort by' - = f.submit "Show" + .input-group + = f.select "sort", + options_for_select({ "popularity": 'popular', + "alphabetically": 'alpha' }, + @sort || 'popular'), + label: 'Sort by' + = f.submit "Show" -.pagination= render 'layouts/pagination', collection: @crops - -.crops - .row +%section.crops + %h2= t('.title') + = will_paginate @crops + .index-cards - @crops.each do |crop| - .col-6.col-md-3= render 'crops/thumbnail', crop: crop + = render 'crops/thumbnail', crop: crop - -.pagination= render 'layouts/pagination', collection: @crops + = will_paginate @crops %ul.list-inline %li The data on this page is available in the following formats: diff --git a/app/views/crops/requested.haml b/app/views/crops/requested.haml index bd0f1cfd5..931ee3de2 100644 --- a/app/views/crops/requested.haml +++ b/app/views/crops/requested.haml @@ -6,10 +6,9 @@ .pagination = will_paginate @requested -.row +.index-cards - @requested.each do |crop| - .col-md-2.six-across - = render partial: "thumbnail", locals: { crop: crop } + = render partial: "thumbnail", locals: { crop: crop } .pagination = will_paginate @requested diff --git a/app/views/crops/search.html.haml b/app/views/crops/search.html.haml index 1312f5d50..45610b70d 100644 --- a/app/views/crops/search.html.haml +++ b/app/views/crops/search.html.haml @@ -2,25 +2,23 @@ %li.breadcrumb-item= link_to 'Crops', crops_path %li.breadcrumb-item.active= link_to 'Search', search_crops_path(term: @term) -%section.crops-search-form - - - if @term - - content_for :title, "Crops matching \"#{@term}\"" - %h1 Crops matching "#{@term}" +- if @term + - content_for :title, "Crops matching \"#{@term}\"" + %h1 + Crops matching "#{@term}" - if @crops - %h2.text-muted Found #{@crops.size} total - - else - - content_for :title, "Crop search" - %h1 Crop search + %span.text-muted Found #{@crops.size} total +- else + - content_for :title, "Crop search" + %h1 Crop search - - = bootstrap_form_tag(url: search_crops_path, method: :get, html: { id: 'crop-search'}, layout: :inline) do |f| - = f.label :term, "Search crops:", class: 'sr-only' - = f.text_field 'term', class: 'search-query input-medium', - placeholder: 'Search crops', - label: 'crop', - value: @term - = f.submit "Search", class: 'btn btn-success' += bootstrap_form_tag(url: search_crops_path, method: :get, html: { id: 'crop-search'}, layout: :inline) do |f| + = f.label :term, "Search crops:", class: 'sr-only' + = f.text_field 'term', class: 'search-query input-medium', + placeholder: 'Search crops', + label: 'crop', + value: @term + = f.submit "Search", class: 'btn btn-success' - if @crops.empty? %h2 No results found @@ -32,12 +30,11 @@ instead. - else - .pagination= will_paginate @crops + = will_paginate @crops - .crops - .row - - @crops.each do |crop| - .col-6.col-md-3= render 'crops/thumbnail', crop: crop + .index-cards + - @crops.each do |crop| + = render 'crops/thumbnail', crop: crop - .pagination= will_paginate @crops + = will_paginate @crops diff --git a/app/views/crops/show.html.haml b/app/views/crops/show.html.haml index b7f6d5254..75f5b6d7e 100644 --- a/app/views/crops/show.html.haml +++ b/app/views/crops/show.html.haml @@ -1,7 +1,6 @@ - content_for :title, @crop.name - content_for :opengraph do - - @crop.photos.each do |photo| - = tag("meta", property: "og:image", content: photo.fullsize_url) + = tag("meta", property: "og:image", content: crop_image_path(@crop)) = tag("meta", property: "og:title", content: @crop.name) = tag("meta", property: "og:type", content: "website") = tag("meta", property: "og:url", content: request.original_url) @@ -12,64 +11,63 @@ - content_for :breadcrumbs do %li.breadcrumb-item= link_to 'Crops', crops_path - %li.breadcrumb-item.active= link_to @crop, @crop - -%h1 - %strong= @crop.name.titleize - %small.text-muted= @crop.default_scientific_name -%p= render 'crops/actions', crop: @crop + %li.breadcrumb-item.active= link_to @crop.name.capitalize, @crop = render 'approval_status_message', crop: @crop +.jumbotron= render 'crops/info' +.row + .col-md-9= render 'crops/actions', crop: @crop + .col-md-3= render 'wrangle', crop: @crop .row .col-md-9 - %p - - if !@crop.plantings.empty? - #{@crop.name.titleize} has been planted - = pluralize(@crop.plantings.size, "time") - by #{ENV['GROWSTUFF_SITE_NAME']} members. - - else - Nobody is growing this yet. You could be the first! - - if @crop.perennial? - #{@crop.name.capitalize} is a perennial crop (living more than two years) - - elsif @crop.annual? - #{@crop.name.capitalize} is an annual crop (living and reproducing in a single year or less) - %hr/ + %section.prediction + = cute_icon + = render 'predictions', crop: @crop + - if @crop.companions.any? + %section.companions + %h2 Companions + - @crop.companions.each do |companion| + = render 'crops/tiny', crop: companion - = render 'predictions', crop: @crop - %hr/ - = render 'crops/photos', photos: @photos, crop: @crop + %section.photos + = cute_icon + = render 'crops/photos', photos: @photos, crop: @crop - .card-deck.text-center - .col-md-4 - %h3.section-heading.h3.pt-4 Sunniness - = pie_chart crop_sunniness_path(@crop, format: :json), legend: "bottom" - .col-md-4 - %h3.section-heading.h3.pt-4 Planted from - = pie_chart crop_planted_from_path(@crop, format: :json), legend: "bottom" - .col-md-4 - %h3.section-heading.h3.pt-4 Harvested for - = pie_chart crop_harvested_for_path(@crop, format: :json), legend: "bottom" + - if @crop.plantings.any? + %section.charts + .row + .col-lg-4.col-12 + %h2 Sunniness + = pie_chart crop_sunniness_path(@crop, format: :json), legend: "bottom" + .col-lg-4.col-12 + %h2 Planted from + = pie_chart crop_planted_from_path(@crop, format: :json), legend: "bottom" + .col-lg-4.col-12 + %h2 Harvested for + = pie_chart crop_harvested_for_path(@crop, format: :json), legend: "bottom" - .varieties= render 'varieties', crop: @crop + - if @crop.varieties.any? + %section.varieties + %h2 Varieties + .index-cards + = render 'varieties', crop: @crop - %h3 - = icon 'fas', 'map' - Crop Map - %p - Only plantings by members who have set their locations are shown on this map. - - if current_member && current_member.location.blank? - = link_to "Set your location.", edit_member_registration_path - #cropmap + %section.crop-map + %h2 + = icon 'fas', 'map' + Crop Map + %p + Only plantings by members who have set their locations are shown on this map. + - if current_member && current_member.location.blank? + = link_to "Set your location.", edit_member_registration_path + #cropmap.map - = render 'crops/posts', crop: @crop + %section.posts= render 'crops/posts', crop: @crop .col-md-3 - = render 'wrangle', crop: @crop + = cute_icon .card - .crop-image - = image_tag crop_image_path(@crop, full_size: true), - class: 'img-card', alt: 'photo of crop' .card-body %h4 How to grow #{@crop.name.pluralize} = render 'grown_for', crop: @crop @@ -111,6 +109,7 @@ = render 'harvests', crop: @crop = render 'find_seeds', crop: @crop + = cute_icon .card .card-body %h5.card-title Learn more about #{@crop.name.pluralize} @@ -123,7 +122,7 @@ Wikipedia (English) %li.list-group-item - = link_to "https://openfarm.cc/en/crops/#{CGI.escape @crop.name}", + = link_to "https://openfarm.cc/en/crops/#{CGI.escape @crop.name.gsub(' ', '-')}", class: 'card-link', target: "_blank", rel: "noopener noreferrer" do diff --git a/app/views/devise/registrations/_edit_profile.html.haml b/app/views/devise/registrations/_edit_profile.html.haml index 054570f66..57132d809 100644 --- a/app/views/devise/registrations/_edit_profile.html.haml +++ b/app/views/devise/registrations/_edit_profile.html.haml @@ -25,7 +25,7 @@ %br/ To change your profile picture, visit = succeed "." do - = link_to 'gravatar.com', "http://gravatar.com/" + = link_to 'gravatar.com', "https://gravatar.com/" .form-group .form-actions.col-md-offset-2.col-md-8 diff --git a/app/views/follows/followers.html.haml b/app/views/follows/followers.html.haml index 1aea716bd..f6a005797 100644 --- a/app/views/follows/followers.html.haml +++ b/app/views/follows/followers.html.haml @@ -1,16 +1,11 @@ - content_for :title, "#{@member.login_name}'s followers" -.pagination - = page_entries_info @followers - = will_paginate @followers += page_entries_info @followers += will_paginate @followers -.row - .col-md-12 - - @followers.each do |f| - .col-md-4.three-across - .thumbnail - = render partial: "members/thumbnail", locals: { member: f } +.index-cards + - @followers.each do |f| + .thumbnail= render partial: "members/thumbnail", locals: { member: f } -.pagination - = page_entries_info @followers - = will_paginate @followers += page_entries_info @followers += will_paginate @followers diff --git a/app/views/follows/index.html.haml b/app/views/follows/index.html.haml index ecd7bae2e..ec7d97d35 100644 --- a/app/views/follows/index.html.haml +++ b/app/views/follows/index.html.haml @@ -1,16 +1,11 @@ - content_for :title, "#{@member.login_name}'s follows" -.pagination - = page_entries_info @follows - = will_paginate @follows +%h1.page-title #{@member.login_name}'s follows -.row - .col-md-12 - - @follows.each do |f| - .col-md-4.three-across - .thumbnail - = render partial: "members/thumbnail", locals: { member: f } += page_entries_info @follows += will_paginate @follows -.pagination - = page_entries_info @follows - = will_paginate @follows +.index-cards= render @follows + += page_entries_info @follows += will_paginate @follows diff --git a/app/views/garden_types/index.html.haml b/app/views/garden_types/index.html.haml index 49352bc4c..d40e16ae9 100644 --- a/app/views/garden_types/index.html.haml +++ b/app/views/garden_types/index.html.haml @@ -4,10 +4,9 @@ #{ENV['GROWSTUFF_SITE_NAME']} tracks who's growing what, where. View any garden_type page to see which of our members have used it. -.row +.index-cards - @garden_types.each do |garden_type| - .col-md-2.six-across - = render partial: "thumbnail", locals: { garden_type: garden_type } + = render partial: "thumbnail", locals: { garden_type: garden_type } - if can? :create, GardenType %div diff --git a/app/views/gardens/_actions.html.haml b/app/views/gardens/_actions.html.haml index 715dcdf38..554d7e3fa 100644 --- a/app/views/gardens/_actions.html.haml +++ b/app/views/gardens/_actions.html.haml @@ -1,5 +1,5 @@ - if can?(:edit, garden) - .dropdown.float-right.garden-actions + .dropdown.garden-actions %a#garden-actions-button.btn.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", type: "button", href: '#'} Actions .dropdown-menu.dropdown-menu-xs{"aria-labelledby" => "garden-actions-button"} - if can?(:edit, garden) diff --git a/app/views/gardens/_garden.html.haml b/app/views/gardens/_garden.html.haml new file mode 100644 index 000000000..bba2a0ee4 --- /dev/null +++ b/app/views/gardens/_garden.html.haml @@ -0,0 +1,5 @@ +.card + = link_to garden do + = image_tag garden_image_path(garden), class: 'img-card', alt: garden + .card-body.text-center + %h4.card-title= garden.name \ No newline at end of file diff --git a/app/views/gardens/_previously.haml b/app/views/gardens/_previously.haml new file mode 100644 index 000000000..c0c8d9968 --- /dev/null +++ b/app/views/gardens/_previously.haml @@ -0,0 +1,18 @@ +%h2 Previously planted in this garden + +- if @finished_plantings.any? + - year = nil + - @finished_plantings.where.not(planted_at: nil).order(planted_at: :desc).each do |planting| + - if year != planting.planted_at.year + - year = planting.planted_at.year + %h4= year + = render "plantings/tiny", planting: planting + + - if @finished_plantings.where(planted_at: nil).any? + %h4 Unknown year + - @finished_plantings.where(planted_at: nil).each do |planting| + = render "plantings/tiny", planting: planting +- else + .col-md-12 + %p Nothing has been planted here. + diff --git a/app/views/gardens/index.html.haml b/app/views/gardens/index.html.haml index 0515ad7ef..7e59e143e 100644 --- a/app/views/gardens/index.html.haml +++ b/app/views/gardens/index.html.haml @@ -1,8 +1,7 @@ - content_for :title, @owner ? "#{@owner}'s gardens" : "Everyone's gardens" -%h1 - = @owner ? "#{@owner}'s gardens" : "Everyone's gardens" +%h1= @owner ? "#{@owner}'s gardens" : "Everyone's gardens" = render 'layouts/nav', model: Garden @@ -14,7 +13,7 @@ %li.breadcrumb-item.active= link_to 'Gardens', gardens_path %section.border-top - = link_to gardens_active_tickbox_path(@owner, @show_all), class: 'btn' do + = link_to show_inactive_tickbox_path('gardens', owner: @owner, show_all: @show_all), class: 'btn' do = check_box_tag 'active', 'all', @show_all include in-active @@ -24,26 +23,31 @@ = link_to 'Add a garden', new_garden_path, class: 'btn btn-primary' - else - .row - .col-12= page_entries_info @gardens - .col-12= will_paginate @gardens + = page_entries_info @gardens + = will_paginate @gardens - @gardens.each do |garden| - %section.border-top - .row - .col-md-2.border-right - %h2 - %strong= link_to garden, garden - = render 'gardens/actions', garden: garden - %p - = pluralize(garden.plantings.active.size, "planting") - .col-md-10 - .row - - if garden.plantings.empty? - = garden_plant_something_button(garden, classes: 'btn btn-success') - - else - - garden.plantings.active.each do |planting| - .col-6.col-md-4.col-lg-3= render 'plantings/card', planting: planting - + %section.card + %h2= link_to garden.name, garden + .card-header + .row + .col-12.col-md-3 + - unless @owner.present? + owner: + = render 'members/tiny', member: garden.owner + = image_tag garden_image_path(garden), alt: garden.name, class: 'img-card' + .col-12.col-md-2= render 'gardens/actions', garden: garden + .col + - if garden.plantings.active.perennial.any? + %strong Perennials: + - garden.plantings.active.perennial.each do |planting| + = link_to planting do + = crop_icon planting.crop + = planting.crop + .card-body + - if garden.plantings.active.annual.any? + = render 'plantings/progress_list', plantings: garden.plantings.active.annual + - else + No annual plantings .row .col-12= page_entries_info @gardens diff --git a/app/views/gardens/show.html.haml b/app/views/gardens/show.html.haml index cc97c141f..4bd44b740 100644 --- a/app/views/gardens/show.html.haml +++ b/app/views/gardens/show.html.haml @@ -10,20 +10,16 @@ = tag("meta", property: "og:url", content: request.original_url) = tag("meta", property: "og:site_name", content: ENV['GROWSTUFF_SITE_NAME']) -- content_for :scripts do - = javascript_include_tag "charts" - = javascript_include_tag "https://www.gstatic.com/charts/loader.js" - - - content_for :breadcrumbs do %li.breadcrumb-item= link_to 'Gardens', gardens_path %li.breadcrumb-item.active= link_to @garden.name, gardens_path(@garden) .row - .col-md-9.col-sm-12 + .col-md-9.col-12 %h2.h1 %strong= @garden - = render 'gardens/actions', garden: @garden + .col-md-3.col-12 + = render 'gardens/actions', garden: @garden .row .col-md-9 - unless @garden.active @@ -32,7 +28,6 @@ - if can? :edit, @garden = link_to 'Set it to active', edit_garden_path(@garden) to plant something in this garden. - %div %p :growstuff_markdown @@ -46,14 +41,14 @@ Why not = link_to 'tell us more.', edit_garden_path(@garden) - .row - .col-md-12 - %section - %h3 Garden timeline - = timeline garden_timeline_path(@garden), adapter: "google" + - if @garden.plantings.where.not(planted_at: nil).any? + %section.card + %h2 Garden progress + .card-body + = render 'plantings/progress_list', plantings: @garden.plantings.active %section - %h3.h3 Current plantings in garden + %h2 Current plantings in garden .index-cards - if @current_plantings.size.positive? - @current_plantings.each do |planting| @@ -61,18 +56,18 @@ - else .col-md-12 %p Nothing is currently planted here. - %section - %h3.h3 Previously planted in this garden - .index-cards - - if @finished_plantings.size.positive? - - @finished_plantings.each do |planting| - = render "plantings/thumbnail", planting: planting - - else - %p Nothing has been planted here. + + %section.companions + %h2 Suggestioned companions + - @suggested_companions.each do |companion| + = render 'crops/tiny', crop: companion + + %section= render 'previously' + .col-md-3 .card .card-image - = image_tag garden_image_path(@garden, full_size: true), class: 'img-card', alt: 'photo of this garden' + = image_tag garden_image_path(@garden), class: 'img-card', alt: 'photo of this garden' .card-body %h4 About this garden %p @@ -86,7 +81,7 @@ %p %strong Area: = pluralize(@garden.area, @garden.area_unit) - %hr/ + .card .card-header %h4 #{@garden.owner}'s gardens @@ -120,7 +115,8 @@ = add_photo_button(@garden) - if @garden.photos.size.positive? - %h3= localize_plural(@garden.photos, Photo) - .row - - @garden.photos.includes(:owner).each do |photo| - .col-xs-6= render 'photos/thumbnail', photo: photo + %section.photos + %h2= localize_plural(@garden.photos, Photo) + .index-cards + - @garden.photos.includes(:owner).each do |photo| + = render 'photos/thumbnail', photo: photo diff --git a/app/views/harvests/_actions.html.haml b/app/views/harvests/_actions.html.haml index 7cb6ca9a0..cafe42811 100644 --- a/app/views/harvests/_actions.html.haml +++ b/app/views/harvests/_actions.html.haml @@ -1,6 +1,6 @@ - if can?(:edit, harvest) - .dropdown.float-right.harvest-actions - %a#harvest-actions-button.btn.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", type: "button", href: '#'} Actions + .dropdown.harvest-actions + %a#harvest-actions-button.btn.btn-info.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", type: "button", href: '#'} Actions .dropdown-menu.dropdown-menu-xs{"aria-labelledby" => "harvest-actions-button"} = harvest_edit_button(harvest, classes: 'dropdown-item') = add_photo_button(harvest, classes: 'dropdown-item') diff --git a/app/views/harvests/_card.html.haml b/app/views/harvests/_card.html.haml index 2cb4ff07b..53ddcc3d0 100644 --- a/app/views/harvests/_card.html.haml +++ b/app/views/harvests/_card.html.haml @@ -1,17 +1,16 @@ .card = link_to harvest do - = image_tag harvest_image_path(harvest, full_size: true), alt: harvest, class: 'img-card' + = image_tag harvest_image_path(harvest), alt: harvest, class: 'img-card' .card-body %h5 - %strong= link_to "#{harvest.crop} #{harvest.plant_part}", harvest - %span.badge.badge-pill.badge-info= link_to harvest.plant_part, harvest.plant_part - + = crop_icon(harvest.crop) + %strong + = link_to harvest.crop, harvest + %span.badge.badge-pill= harvest.plant_part - if harvest.planting.present? %p.card-text %small.text-muted Harvested from = link_to harvest.planting, harvest.planting .card-footer - %p - .float-left= link_to harvest.owner, harvest.owner - .float-right=render 'members/tiny', member: harvest.owner \ No newline at end of file + .float-right=render 'members/tiny', member: harvest.owner \ No newline at end of file diff --git a/app/views/harvests/_owner.html.haml b/app/views/harvests/_owner.html.haml index f51da33f8..977743c15 100644 --- a/app/views/harvests/_owner.html.haml +++ b/app/views/harvests/_owner.html.haml @@ -1,18 +1,19 @@ -.well - .row - .col-md-6 - %h4 - Harvested by - = link_to harvest.owner, harvest.owner - = link_to "view all #{harvest.owner}'s harvests", member_gardens_path(harvest.owner) +.card + .card-body + .row + .col-md-6 + %h4 + Harvested by + = link_to harvest.owner, harvest.owner + = link_to "view all #{harvest.owner}'s harvests", member_harvests_path(harvest.owner) - - if harvest.planting.present? - %p - Harvested from - = link_to harvest.planting, harvest.planting - - if harvest.owner.location - %p - %small - View other harvests, members and more near - = link_to harvest.owner.location, place_path(harvest.owner.location, anchor: "harvests") - .col-md-6= render "members/avatar", member: harvest.owner \ No newline at end of file + - if harvest.planting.present? + %p + Harvested from + = link_to harvest.planting, harvest.planting + - if harvest.owner.location + %p + %small + View other harvests, members and more near + = link_to harvest.owner.location, place_path(harvest.owner.location, anchor: "harvests") + .col-md-6= render "members/avatar", member: harvest.owner \ No newline at end of file diff --git a/app/views/harvests/_planting.haml b/app/views/harvests/_planting.haml index ae2d94b9c..5e1eaafe8 100644 --- a/app/views/harvests/_planting.haml +++ b/app/views/harvests/_planting.haml @@ -1,15 +1,7 @@ -- if @harvest.planting - = link_to "#{@harvest.planting.crop.name} planted on #{@harvest.planting.planted_at}", - planting_path(@harvest.planting) - in - = link_to @harvest.planting.garden, garden_path(@harvest.planting.garden) -- elsif @matching_plantings && @matching_plantings.any? && @harvest.owner == current_member - +- if @harvest.planting.nil? && @matching_plantings && @matching_plantings.any? && @harvest.owner == current_member .alert.alert-info{role: "alert"} = bootstrap_form_for(@harvest) do |f| Is this from one of these plantings? - @matching_plantings.each do |planting| = f.radio_button :planting_id, planting.id, label: planting = f.submit "save", class: 'btn btn-sm' -- else - Unknown diff --git a/app/views/harvests/_thumbnail.html.haml b/app/views/harvests/_thumbnail.html.haml index f29828b4f..8539d72d8 100644 --- a/app/views/harvests/_thumbnail.html.haml +++ b/app/views/harvests/_thumbnail.html.haml @@ -1,10 +1,9 @@ -.thumbnail - .harvest-thumbnail - - if harvest - = link_to image_tag(harvest_image_path(harvest), - alt: harvest.crop.name, class: 'img'), - harvest - .harvestinfo - .harvest-name - = link_to display_quantity(harvest), harvest - = I18n.l(harvest.harvested_at.to_date) +.card.harvest-thumbnail + = link_to image_tag(harvest_image_path(harvest), + alt: harvest, + class: 'img img-card'), + harvest + + .text + %h3.harvest-plant-part= link_to harvest.plant_part, harvest + %h5.harvest-crop= harvest.crop diff --git a/app/views/harvests/index.html.haml b/app/views/harvests/index.html.haml index 3434f4e92..dd0ccfc29 100644 --- a/app/views/harvests/index.html.haml +++ b/app/views/harvests/index.html.haml @@ -29,16 +29,13 @@ .index-cards=render @harvests, full: true .pagination - = page_entries_info @harvests = will_paginate @harvests - %ul.list-inline - %li The data on this page is available in the following formats: - - if @owner - %li= link_to "RSS", member_harvests_path(@owner, format: 'rss') - %li= link_to "CSV", member_harvests_path(@owner, format: 'csv') - %li= link_to "JSON", member_harvests_path(@owner, format: 'json') - - else - %li= link_to "RSS", harvests_path(format: 'rss') - %li= link_to "CSV", harvests_path(format: 'csv') - %li= link_to "JSON", harvests_path(format: 'json') +%section.open-data + %h5= t('label.data') + %ul.nav#open-data + - ['csv', 'json', 'rss'].each do |format| + %li.list-group-item + = link_to (@owner ? member_harvests_path(@owner, format: format) : harvests_path(format: format)) do + = icon 'fas', format.to_s + = format.upcase \ No newline at end of file diff --git a/app/views/harvests/show.html.haml b/app/views/harvests/show.html.haml index 116a7ec84..b5f98b55e 100644 --- a/app/views/harvests/show.html.haml +++ b/app/views/harvests/show.html.haml @@ -8,46 +8,60 @@ = tag("meta", property: "og:url", content: request.original_url) = tag("meta", property: "og:site_name", content: ENV['GROWSTUFF_SITE_NAME']) -.row - .col-md-8 - %h1 - = harvest_icon - #{@harvest.crop} harvested by #{@harvest.owner} - = render 'harvests/actions', harvest: @harvest - = link_to "view all #{@harvest.owner}'s harvests >>", member_harvests_path(@harvest.owner), class: 'btn' - .index-cards.planting-facts - - if @harvest.plant_part - .card.planting-fact-card - %h3 Plant part - %span= link_to @harvest.plant_part, @harvest.plant_part - - if @harvest.harvested_at.present? - .card.planting-fact-card - %h3 Harvested - = @harvest.harvested_at +- content_for :breadcrumbs do + %li.breadcrumb-item= link_to 'Harvests', harvests_path + %li.breadcrumb-item= link_to @harvest.owner, member_harvests_path(@harvest.owner) + %li.breadcrumb-item.active= link_to @harvest, @harvest - .card.planting-fact-card - %h3 Planting - = render partial: 'planting' - .card.planting-fact-card - %h3 Quantity - %span= display_quantity(@harvest) +.harvest + .row + .col-md-8.col-xs-12 + %h1 + = harvest_icon + #{@harvest.crop} harvested by #{@harvest.owner} + .col-md-4.col-xs-12 + = render 'harvests/actions', harvest: @harvest + .col-md-8.col-xs-12 + = render partial: 'planting' + .index-cards.facts + - if @harvest.plant_part + .card + %h3 + Plant part + = editable :select, @harvest, :plant_part_id, collection: PlantPart.all.pluck(:name, :id), display_field: '.harvest-plantpart' + %strong.harvest-plantpart= @harvest.plant_part + .card + %h3 + Harvested + = editable :date, @harvest, :harvested_at, display_field: '.harvested_at' + %strong.harvested_at #{time_ago_in_words @harvest.harvested_at} ago + %span.harvested_at= I18n.l @harvest.harvested_at + .card{class: @harvest.quantity.present? ? '' : 'text-muted'} + %h3 + Quantity + = editable :text_field, @harvest, :quantity, display_field: '.quantity' + %strong.quantity + = display_quantity(@harvest) - .col-md-4 - .card - .card-body= render 'harvests/owner', harvest: @harvest + - if @harvest.photos.size.positive? + %section + %h2 Photos + = render 'photos/gallery', photos: @harvest.photos.order(date_taken: :desc).limit(3) + %section.harvest-detail + %h2 Detail + - if @harvest.planting.present? + Havested from + = link_to @harvest.planting, @harvest.planting - = render @harvest.crop + - if @harvest.description.present? + .card + .card-header + %h2 Notes + .card-body + :growstuff_markdown + #{strip_tags(@harvest.description)} - %hr/ - - if @harvest.photos.size.positive? - .row - .col-md-6= render @harvest.default_photo - .col-md-6 - = render 'photos/gallery', photos: @harvest.photos.order(date_taken: :desc).limit(3).offset(1) - %p.text-right= link_to 'View all photos >>', harvest_photos_path(@harvest), class: 'btn' - .col-md-4 - .card - .card-body= render 'harvests/owner', harvest: @harvest - - = render @harvest.crop \ No newline at end of file + .col-md-4.col-xs-12 + = render 'harvests/owner', harvest: @harvest + = render @harvest.crop diff --git a/app/views/home/_blurb.html.haml b/app/views/home/_blurb.html.haml index 9c942acad..e65a1c396 100644 --- a/app/views/home/_blurb.html.haml +++ b/app/views/home/_blurb.html.haml @@ -1,6 +1,6 @@ .row .col-md-8.info - %h1= ENV['GROWSTUFF_SITE_NAME'] + %h1.display-3= ENV['GROWSTUFF_SITE_NAME'] %p= t('.intro', site_name: ENV['GROWSTUFF_SITE_NAME']) = render 'stats' .col-md-4 diff --git a/app/views/home/_crops.html.haml b/app/views/home/_crops.html.haml index 974abdb63..b463948c8 100644 --- a/app/views/home/_crops.html.haml +++ b/app/views/home/_crops.html.haml @@ -1,6 +1,5 @@ - cache cache_key_for(Crop, 'homepage'), expires_in: 1.day do - .row - - Crop.interesting.includes(:scientific_names, :photos).shuffle.first(12).each do |crop| - .col-6.col-md-3 - = render 'crops/thumbnail', crop: crop + .index-cards + - Crop.interesting.includes(:scientific_names, :photos).shuffle.first(18).each do |crop| + = render 'crops/thumbnail', crop: crop diff --git a/app/views/home/_harvests.html.haml b/app/views/home/_harvests.html.haml index aab9c71d4..05948de40 100644 --- a/app/views/home/_harvests.html.haml +++ b/app/views/home/_harvests.html.haml @@ -1,12 +1,10 @@ -%section.harvests - %h5= t('.recently_harvested') - - Harvest.has_photos.recent.includes(:crop, :owner, :photos).limit(6).each do |harvest| - .card - = link_to harvest, class: 'list-group-item list-group-item-action flex-column align-items-start' do - .d-flex.w-100.justify-content-between - %div - %h5.mb-2.h5= harvest.crop.name - %span.badge.badge-success=harvest.plant_part - %small.text-muted - harvested by #{harvest.owner} - %p.mb-2= image_tag harvest_image_path(harvest), width: 75, class: 'rounded shadow' \ No newline at end of file +%h2= t('.recently_harvested') +- Harvest.has_photos.recent.includes(:crop, :owner, :photos).limit(6).each do |harvest| + = link_to harvest, class: 'list-group-item list-group-item-action flex-column align-items-start' do + .d-flex.w-100.justify-content-between.homepage--list-item + %div + %h5= harvest.crop.name + %span.badge.badge-success=harvest.plant_part + %small.text-muted + harvested by #{harvest.owner} + %p.mb-2= image_tag harvest_image_path(harvest), width: 75, class: 'rounded shadow' \ No newline at end of file diff --git a/app/views/home/_members.html.haml b/app/views/home/_members.html.haml index 90d4c7078..c055ba59a 100644 --- a/app/views/home/_members.html.haml +++ b/app/views/home/_members.html.haml @@ -3,8 +3,7 @@ - members = Member.includes(plantings: :crop).interesting.limit(6) - if members.present? %h2.text-center= t('.title') - .member-cards.index-cards + .index-cards = render members - %p.text-right = link_to "#{t('.view_all')} »", members_path, class: 'btn btn-block' diff --git a/app/views/home/_plantings.html.haml b/app/views/home/_plantings.html.haml index c06951ada..a148a05fe 100644 --- a/app/views/home/_plantings.html.haml +++ b/app/views/home/_plantings.html.haml @@ -1,13 +1,12 @@ -%section.plantings - %h5= t('.recently_planted') +%h2= t('.recently_planted') - - Planting.has_photos.recent.includes(:crop, garden: :owner).limit(6).each do |planting| - .card.planting-card - = link_to planting, class: 'list-group-item list-group-item-action flex-column align-items-start' do - .d-flex.w-100.justify-content-between - %p.mb-2= image_tag planting_image_path(planting), width: 75, class: 'rounded shadow' - .text-right - %h5.mb-2.h5= planting.crop.name - - if planting.planted_from.present? - %span.badge.badge-success= planting.planted_from.pluralize - %small.text-muted= display_planting(planting) \ No newline at end of file +- Planting.has_photos.recent.includes(:crop, garden: :owner).limit(6).each do |planting| + = link_to planting, class: 'list-group-item list-group-item-action flex-column align-items-start' do + .d-flex.w-100.justify-content-between.homepage--list-item + %p.mb-2 + = image_tag planting_image_path(planting), width: 75, class: 'rounded shadow' + .text-right + %h5= planting.crop.name + - if planting.planted_from.present? + %span.badge.badge-success= planting.planted_from.pluralize + %small.text-muted planted by #{planting.owner} \ No newline at end of file diff --git a/app/views/home/_seeds.html.haml b/app/views/home/_seeds.html.haml index bff02e70d..38ae29fd9 100644 --- a/app/views/home/_seeds.html.haml +++ b/app/views/home/_seeds.html.haml @@ -1,5 +1,5 @@ - cache cache_key_for(Seed) do %h2.text-center= t('home.seeds.title') - .homepage-cards + .index-cards - Seed.current.tradable.includes(:owner, :crop).order(created_at: :desc).limit(6).each do |seed| = render 'seeds/card', seed: seed diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index dd748925f..e5caaeab3 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -2,35 +2,48 @@ = ENV['GROWSTUFF_SITE_NAME'] - if member_signed_in? - %h1.display-5= t('.welcome', site_name: ENV['GROWSTUFF_SITE_NAME'], member_name: current_member) + .row + .col-lg-8.col-md-12 + %h1.display-4= t('.welcome', site_name: ENV['GROWSTUFF_SITE_NAME'], member_name: current_member) - %p= render 'stats' + %p= render 'stats' + .col + %p + = link_to member_gardens_path(current_member), class: 'btn btn-dark' do + = image_icon 'gardens' + Show me my garden - else .hidden-xs - .jumbotron= render 'blurb' - + %section.jumbotron= render 'blurb' .row - .col-xl-8.col-md-12 + .col-lg-8.col-md-12 %section.crops + = cute_icon %h2= t('home.crops.our_crops') - .homepage-cards= render 'crops' - .align-bottom - %p.text-right= link_to "#{t('home.crops.view_all')} »", crops_path, class: 'btn btn-block' - - cache cache_key_for(Crop, 'recent') do - %h3= t('.recently_added') + = render 'crops' + = link_to "#{t('home.crops.view_all')} »", crops_path, class: 'btn btn-block' + %section.recent-crops + - cache cache_key_for(Crop, 'recent') do + %h2= t('.recently_added') + %p != Crop.recent.limit(30).map { |c| link_to(c, c) }.join(", ") - .col-xl-4 - .row - .col-md-6 - =render 'plantings' - %p.text-right= link_to "#{t('home.plantings.view_all')} »", plantings_path, class: 'btn btn-block' - .col-md-6 - = render 'harvests' - %p.text-right= link_to "#{t('home.harvests.view_all')} »", harvests_path, class: 'btn btn-block' - .col-md-6 - %section.seeds.mx-auto + + .col-xl-2.col + %section.plantings + = cute_icon + =render 'plantings' + %p.text-right= link_to "#{t('home.plantings.view_all')} »", plantings_path, class: 'btn btn-block' + .col-xl-2.col + %section.harvests + = cute_icon + = render 'harvests' + %p.text-right= link_to "#{t('home.harvests.view_all')} »", harvests_path, class: 'btn btn-block' + .col-12.col-md-6 + %section.seeds + = cute_icon = render 'seeds' %p.text-right= link_to "#{t('home.seeds.view_all')} »", seeds_path, class: 'btn btn-block' - .col-md-6 - %section.members.mx-auto= render 'members' - %section.discussion.mx-auto= render 'discuss' \ No newline at end of file + .col-12.col-md-6 + %section.discussion= render 'discuss' + .col-12 + %section.members= render 'members' diff --git a/app/views/layouts/_fact_card.haml b/app/views/layouts/_fact_card.haml new file mode 100644 index 000000000..daaa36468 --- /dev/null +++ b/app/views/layouts/_fact_card.haml @@ -0,0 +1,6 @@ +.card.fact-card + .card-body.text-center + %h3= title + %strong= value + - if description.present? + %span= description \ No newline at end of file diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml index 9376d9a02..3783b7059 100644 --- a/app/views/layouts/_header.html.haml +++ b/app/views/layouts/_header.html.haml @@ -1,20 +1,58 @@ %nav.navbar.navbar-expand-lg.navbar-dark.default-color.bg-dark - %a.navbar-brand.d-xs-none{ href: root_path } - = image_tag("growstuff-brand.png", size: "200x50", alt: ENV['GROWSTUFF_SITE_NAME']) - %a.navbar-brand.d-none.d-xs{ href: root_path } - = image_tag("growstuff-apple-touch-icon-precomposed.png", - size: "40x40", alt: ENV['GROWSTUFF_SITE_NAME']) - %button.navbar-toggler{"aria-controls" => "navbarSupportedContent", "aria-expanded" => "false", "aria-label" => "Toggle navigation", "data-target" => "#navbarSupportedContent", "data-toggle" => "collapse", type: "button"} - %span.navbar-toggler-icon + .row + .col-2.col-lg-4 + .d-none.d-lg-inline + %a.navbar-brand{ href: root_path } + = image_tag("growstuff-brand.png", size: "200x50", alt: ENV['GROWSTUFF_SITE_NAME']) + .d-inline.d-lg-none + %a.navbar-brand{ href: root_path } + = image_tag("growstuff-apple-touch-icon-precomposed.png", size: "40x40", + alt: ENV['GROWSTUFF_SITE_NAME']) + .col-8.col-lg-7 + = render 'crops/search_bar' + .col-2.col-lg-1 + %button.navbar-toggler{"aria-controls" => "navbarSupportedContent", "aria-expanded" => "false", "aria-label" => "Toggle navigation", "data-target" => "#navbarSupportedContent", "data-toggle" => "collapse", type: "button"} + %i.fas.fa-ellipsis-v.navbar-toggler-icon + #navbarSupportedContent.collapse.navbar-collapse %ul.navbar-nav.mr-auto + - if signed_in? + = link_to timeline_index_path, method: :get, class: 'nav-link text-white' do + = image_tag 'icons/notification.svg', class: 'img img-icon' + = link_to member_gardens_path(member_slug: current_member.slug), class: 'nav-link text-white' do + = image_icon 'gardens' + %li.nav-item.dropdown + %a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", href: "#", role: "button"} + = image_tag "icons/gardener.svg", class: 'img img-icon' + = t('.record') + .dropdown-menu + = link_to new_planting_path, class: 'dropdown-item' do + = image_icon('planting-add') + = t('buttons.new_planting') + = link_to new_harvest_path, class: 'dropdown-item' do + = image_icon('harvest-add') + = t('buttons.new_harvest') + = link_to new_seed_path, class: 'dropdown-item' do + = image_icon('seed-add') + = t('buttons.new_seeds') + = link_to new_post_path, class: 'dropdown-item' do + = post_icon + = t('buttons.new_post') + %li.nav-item.dropdown - %a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", :href => "#", :role => "button"}= t('.crops') + %a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", href: "#", role: "button"}= t('.crops') .dropdown-menu - = link_to t('.browse_crops'), crops_path, class: 'dropdown-item' - = link_to t('.seeds'), seeds_path, class: 'dropdown-item' - = link_to t('.plantings'), plantings_path, class: 'dropdown-item' - = link_to t('.harvests'), harvests_path, class: 'dropdown-item' + = link_to crops_path, class: 'dropdown-item' do + = t('.browse_crops') + = link_to seeds_path, class: 'dropdown-item' do + = seed_icon + = t('.seeds') + = link_to plantings_path, class: 'dropdown-item' do + = planting_icon + = t('.plantings') + = link_to harvests_path, class: 'dropdown-item' do + = harvest_icon + = t('.harvests') %li.nav-item.dropdown %a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", href: "#", role: "button"}= t('.community') @@ -24,7 +62,6 @@ = link_to t('.posts'), posts_path, class: 'dropdown-item' = link_to t('.forums'), forums_path, class: 'dropdown-item' - - if member_signed_in? %li.nav-item.dropdown %a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", href: "#", role: "button"} @@ -70,4 +107,3 @@ - else %li.nav-item= link_to t('.sign_in'), new_member_session_path, id: 'navbar-signin', class: 'btn nav-link' %li.nav-item= link_to t('.sign_up'), new_member_registration_path, id: 'navbar-signup', class: 'btn nav-link' - = render 'crops/search_bar' diff --git a/app/views/layouts/_nav.haml b/app/views/layouts/_nav.haml index 6af1aaa10..7122a0e20 100644 --- a/app/views/layouts/_nav.haml +++ b/app/views/layouts/_nav.haml @@ -1,14 +1,15 @@ -- content_for :buttonbar do - - if current_member.present? - = link_to url_for([current_member, model]), class: 'btn' do - My #{model.model_name.human.pluralize} +- if current_member.present? + %ul.nav.crop-actions + %li.nav-item.dropdown.btn.btn-sm + = link_to url_for([current_member, model]), class: 'nav-link' do + My #{model.model_name.human.pluralize} + %li.nav-item.dropdown.btn.btn-sm + = link_to model, class: 'nav-link' do + Everyone's #{model.model_name.human.pluralize} + - if can?(:create, model) + %li.nav-item.dropdown.btn.btn-sm + = link_to url_for([model, action: :new]), class: 'nav-link' do + Add a #{model.model_name.human} - = link_to model, class: 'btn' do - Everyone's #{model.model_name.human.pluralize} - - - if can?(:create, model) - = link_to url_for([model, action: :new]), class: 'btn' do - Add a #{model.model_name.human} - -- unless current_member - = render 'shared/signin_signup', to: "add a new #{model.to_s.downcase}" +- else + = render 'shared/signin_signup', to: "record your #{model.to_s.pluralize.downcase}" diff --git a/app/views/layouts/_pagination.html.haml b/app/views/layouts/_pagination.html.haml deleted file mode 100644 index 186c6b32e..000000000 --- a/app/views/layouts/_pagination.html.haml +++ /dev/null @@ -1 +0,0 @@ -= will_paginate collection, renderer: WillPaginate::ActionView::BootstrapLinkRenderer \ No newline at end of file diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 0cfbdd58e..72bb495c5 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -7,19 +7,16 @@ = render "layouts/header" -# anchor tag for accessibility link to skip the navigation menu %a{ name: 'skipnav' } - #maincontainer.container + #maincontainer.container-fluid .row - .col-12 - .float-right= render 'shared/global_actions' + .col-md-8.col-12 - if content_for?(:breadcrumbs) - .float-left - %nav{"aria-label" => "breadcrumb"} - %ol.breadcrumb - %li.breadcrumb-item= link_to 'Home', root_path - = yield(:breadcrumbs) - - - if content_for?(:buttonbar) - .layout-actions.float-right= yield(:buttonbar) + %nav + %ol.breadcrumb{"aria-label" => "breadcrumb"} + %li.breadcrumb-item= link_to 'Home', root_path + = yield(:breadcrumbs) + - if content_for?(:buttonbar) + = yield(:buttonbar) - if content_for?(:subtitle) %small= yield(:subtitle) diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb index cb2589829..eb87fdf68 100644 --- a/app/views/layouts/mailer.html.erb +++ b/app/views/layouts/mailer.html.erb @@ -2,7 +2,40 @@ - + <%= yield %> \ No newline at end of file diff --git a/app/views/layouts/modal.html.haml b/app/views/layouts/modal.html.haml new file mode 100644 index 000000000..8468a6b4c --- /dev/null +++ b/app/views/layouts/modal.html.haml @@ -0,0 +1,11 @@ +#mainModal.modal{"aria-hidden" => "true", "aria-labelledby" => "mainModalLabel", role: "dialog", tabindex: "-1"} + .modal-dialog + .modal-content + .modal-header + %button.close{"data-dismiss" => "modal", type: "button"} + %span{"aria-hidden" => "true"} × + %span.sr-only Close + %h4#mainModalLabel.modal-title + = yield :title if content_for? :title + \ + = yield diff --git a/app/views/members/_location.html.haml b/app/views/members/_location.html.haml index 3b9a2f647..7c08e6acb 100644 --- a/app/views/members/_location.html.haml +++ b/app/views/members/_location.html.haml @@ -1,6 +1,8 @@ -%span.badge.badge-light.member-location - - if member.location.blank? - unknown location - - else - = link_to place_path(member.location, anchor: "members") do - = truncate(member.location, length: 40, separator: ' ', omission: '... ') +- if member.location.present? + = link_to place_path(member.location) do + %span.badge.badge-location + = icon 'fas', 'map-marker' + = truncate(member.location, length: 15, separator: ' ', omission: '... ') +- else + %span.badge.badge-location + unknown location \ No newline at end of file diff --git a/app/views/members/_map.html.haml b/app/views/members/_map.html.haml index 77a46614e..6a2060043 100644 --- a/app/views/members/_map.html.haml +++ b/app/views/members/_map.html.haml @@ -1,5 +1,5 @@ - if member.latitude && member.longitude - #membermap + #membermap.member-map %p.text-right See other members, plantings, seeds and more near = link_to member.location, place_path(member.location, anchor: "members") diff --git a/app/views/members/_member.haml b/app/views/members/_member.haml index 2274a8e5c..7c10ce280 100644 --- a/app/views/members/_member.haml +++ b/app/views/members/_member.haml @@ -11,9 +11,7 @@ = distance_of_time_in_words(member.created_at, Time.zone.now) ago. - if member.location.present? - %span.badge.badge-success - = icon 'fas', 'map-marker' - = truncate(member.location, length: 50, separator: ' ', omission: '... ') + = render 'members/location', member: member %p %ul.nav.nav-justified.small diff --git a/app/views/members/_tiny.haml b/app/views/members/_tiny.haml index dcd4e9987..95b51c2b0 100644 --- a/app/views/members/_tiny.haml +++ b/app/views/members/_tiny.haml @@ -1,4 +1,4 @@ -.member-chip +%span.chip.member-chip - if member.discarded? = icon 'fas', 'user-times' = member diff --git a/app/views/members/nearby.html.haml b/app/views/members/nearby.html.haml index 2d9750c3e..57949ddec 100644 --- a/app/views/members/nearby.html.haml +++ b/app/views/members/nearby.html.haml @@ -17,9 +17,8 @@ - if !@nearby_members.empty? %h3 Results found - .row + .index-cards - @nearby_members.each do |member| - .col-md-4.three-across - = render partial: "members/thumbnail", locals: { member: member } + = render partial: "members/thumbnail", locals: { member: member } - elsif @location %h3 No results found diff --git a/app/views/members/show.html.haml b/app/views/members/show.html.haml index 5e1192c31..693a61d2a 100644 --- a/app/views/members/show.html.haml +++ b/app/views/members/show.html.haml @@ -20,9 +20,9 @@ - @member.roles.each do |role| %span.badge.badge-info= role.name.titleize - if @member.location.present? - %p.badge.badge-success + %p.badge.badge-location = icon 'fas', 'map-marker' - = @member.location + = truncate(@member.location, length: 25, separator: ' ', omission: '... ') %p %strong Member since = @member.created_at.to_s(:date) @@ -56,18 +56,19 @@ facebook_auth: @facebook_auth .col-md-10 - = render "map", member: @member - %h2 Activity - .list-group - - @activity.each do |event| - .list-group-item.list-group-item-action.flex-column.align-items-start{:href => "#!"} - .d-flex.w-100.justify-content-between - %h5 - = icon_for_model(event.event_type) - = event_description(event) - = render 'timeline/photos', photo: resolve_model(event) if event.event_type == 'photo' - %small - - if event.event_at.present? - #{time_ago_in_words(event.event_at)} ago - - else - unknown date + %section= render "map", member: @member + %section.activity + %h2 Activity + .list-group + - @activity.each do |event| + .list-group-item.list-group-item-action.flex-column.align-items-start{:href => "#!"} + .d-flex.w-100.justify-content-between + %h5 + = icon_for_model(event.event_type) + = event_description(event) + = render 'timeline/photos', photo: resolve_model(event) if event.event_type == 'photo' + %small + - if event.event_at.present? + #{time_ago_in_words(event.event_at)} ago + - else + unknown date diff --git a/app/views/notifier/_planting.haml b/app/views/notifier/_planting.haml new file mode 100644 index 000000000..badd7d9c0 --- /dev/null +++ b/app/views/notifier/_planting.haml @@ -0,0 +1,4 @@ += link_to planting do + - if planting.crop.svg_icon.present? + = image_tag crop_url(planting.crop, format: :svg), alt: planting.crop.name, class: 'img-icon' + = planting.crop \ No newline at end of file diff --git a/app/views/notifier/planting_reminder.html.haml b/app/views/notifier/planting_reminder.html.haml index 8f1ce3d27..7bb1174aa 100644 --- a/app/views/notifier/planting_reminder.html.haml +++ b/app/views/notifier/planting_reminder.html.haml @@ -1,63 +1,67 @@ -- site_name = ENV['GROWSTUFF_SITE_NAME'] %p Hello #{@member.login_name}, -%h2 What's new in your garden? - -%p +%h2 Your #{Date.today.strftime("%B %Y")} #{@sitename} progress report - if @member.plantings.empty? %p - #{site_name} lets you track what food you're growing + #{@sitename} lets you track what food you're growing in your garden and see what other people near you are planting too. %p - = link_to "Get started now", new_planting_url - by planting your first crop. + #{link_to "Get started now", new_planting_url} by planting your first crop. - else - %p - The most recent plantings you've told us about are: + - if @harvesting.size.positive? + %section.harvests + %h2 Ready to harvest + %p Congratulations, you have plants ready to harvest + %ul + - @harvesting.each do |planting| + %li= render 'planting', planting: planting - %ul - - @plantings.each do |p| - %li - = link_to p, planting_url(p) - planted - = distance_of_time_in_words(p.created_at, Time.zone.now) - ago. + - if @others.size.positive? + %section.progress + %h2 Progress report + %ul + - @others.each do |planting| + %li + = render 'planting', planting: planting + is #{sprintf '%.0f', planting.percentage_grown}% grown + with #{(planting.finish_predicted_at - Time.zone.today).to_i} days to go. - %p - = link_to "Plant something new", new_planting_url - to keep your garden records up to date. + - if @late.size.positive? + %section.late + %h2 Late + %p + These plantings are at the end of their lifecycle. + %ul + - @late.each do |planting| + %li= render 'planting', planting: planting -%h2 Your recent harvests + - if @super_late.size.positive? + %section.superlate + %h2 Super late + %p + We suspect the following plantings finished long ago and no longer need tracking. You can mark them as finished to stop tracking. + %ul + - @super_late.each do |planting| + %li + = render 'planting', planting: planting + planted on #{planting.planted_at.to_date} +%p + Harvested anything lately? + = link_to "Track your harvests here.", new_harvest_url, class: 'btn' -- if @member.harvests.empty? - %p - #{site_name} helps you keep track of what you - harvest from your garden. Record what food you've grown - and see what other people near you are harvesting, too. +%p + Want to track and predict a planting in your garden? + = link_to "Add a planting.", new_planting_url, class: 'btn' - %p - = link_to "Get started now", new_harvest_url - by tracking your first harvest. +%p + Track and predict your entire garden, and keep your garden records up to date at + = link_to member_gardens_url(@member), class: 'btn' do + your garden overview -- else - According to our records, the last few things you harvested were: - - %ul - - @harvests.each do |h| - %li - = link_to h, harvest_url(h) - harvested - = distance_of_time_in_words(h.created_at, Time.zone.now) - ago. - - %p - Harvested anything else lately? - = link_to "Track your harvests here.", new_harvest_url - -%h2 - See you soon on #{site_name}! +%h4 + See you soon on #{@sitename}! = render partial: 'signature' diff --git a/app/views/photos/_associations.html.haml b/app/views/photos/_associations.html.haml index 5e05c27e2..f036c0851 100644 --- a/app/views/photos/_associations.html.haml +++ b/app/views/photos/_associations.html.haml @@ -1,31 +1,32 @@ -%h4 This photo depicts: -%p - %ul.associations - - @photo.posts.each do |post| - %li - = post_icon - = link_to post.subject, post - = render "association_delete_button", photo: @photo, type: 'post', thing: post - - @photo.plantings.each do |planting| - %li - = planting_icon - = link_to t('photos.show.planting', planting: planting.to_s, owner: planting.owner.to_s), planting_path(planting) - = render "association_delete_button", photo: @photo, type: 'planting', thing: planting +- if @photo.associations? + %h4 This photo depicts: + %p + %ul.associations + - @photo.posts.each do |post| + %li + = post_icon + = link_to post.subject, post + = render "association_delete_button", photo: @photo, type: 'post', thing: post + - @photo.plantings.each do |planting| + %li + = planting_icon + = link_to t('photos.show.planting', planting: planting.to_s, owner: planting.owner.to_s), planting_path(planting) + = render "association_delete_button", photo: @photo, type: 'planting', thing: planting - - @photo.harvests.each do |harvest| - %li - = harvest_icon - = link_to t('photos.show.harvest', crop: harvest.crop.name, owner: harvest.owner.to_s), harvest_path(harvest) - = render "association_delete_button", photo: @photo, type: 'harvest', thing: harvest + - @photo.harvests.each do |harvest| + %li + = harvest_icon + = link_to t('photos.show.harvest', crop: harvest.crop.name, owner: harvest.owner.to_s), harvest_path(harvest) + = render "association_delete_button", photo: @photo, type: 'harvest', thing: harvest - - @photo.gardens.each do |garden| - %li - = garden_icon - = link_to t('photos.show.garden', garden: garden.to_s, owner: garden.owner.to_s), garden_path(garden) - = render "association_delete_button", photo: @photo, type: 'garden', thing: garden + - @photo.gardens.each do |garden| + %li + = garden_icon + = link_to t('photos.show.garden', garden: garden.to_s, owner: garden.owner.to_s), garden_path(garden) + = render "association_delete_button", photo: @photo, type: 'garden', thing: garden - - @photo.seeds.each do |seed| - %li - = seed_icon - = link_to t('photos.show.seed', seed: seed.to_s, owner: seed.owner.to_s), seed_path(seed) - = render "association_delete_button", photo: @photo, type: 'seed', thing: seed + - @photo.seeds.each do |seed| + %li + = seed_icon + = link_to t('photos.show.seed', seed: seed.to_s, owner: seed.owner.to_s), seed_path(seed) + = render "association_delete_button", photo: @photo, type: 'seed', thing: seed diff --git a/app/views/photos/_card.html.haml b/app/views/photos/_card.html.haml index 0d17281f5..f073cbef1 100644 --- a/app/views/photos/_card.html.haml +++ b/app/views/photos/_card.html.haml @@ -1,10 +1,11 @@ .card.photo-card{id: "photo-#{photo.id}"} - = link_to image_tag(photo.fullsize_url, alt: photo.title, class: 'img img-card'), photo + = link_to image_tag(photo.source == 'flickr' ? photo.fullsize_url : photo.thumbnail_url, alt: photo.title, class: 'img img-card'), photo .card-body - %h5.ellipsis= link_to photo.title, photo + %h5.ellipsis + = photo_icon + = link_to photo.title, photo %i by #{link_to photo.owner, photo.owner} - if photo.date_taken.present? - %small - %time{datetime: photo.date_taken} - = I18n.l(photo.date_taken.to_date) + %small.text-muted + %time{datetime: photo.date_taken}= I18n.l(photo.date_taken.to_date) = render 'photos/likes', photo: photo diff --git a/app/views/photos/_gallery.haml b/app/views/photos/_gallery.haml index 98bed3450..e48872d81 100644 --- a/app/views/photos/_gallery.haml +++ b/app/views/photos/_gallery.haml @@ -1,4 +1,4 @@ .index-cards - photos.each do |photo| - .photo-grid-item= render 'photos/card', photo: photo + = render 'photos/card', photo: photo diff --git a/app/views/photos/_hero.html.haml b/app/views/photos/_hero.html.haml new file mode 100644 index 000000000..b269e4431 --- /dev/null +++ b/app/views/photos/_hero.html.haml @@ -0,0 +1,7 @@ +.photo.hero.jumbotron + .row + .col-6 + = image_tag(photo.fullsize_url, alt: photo.title, class: 'img img-responsive hero-photo') + .col-6 + %h3= link_to photo.title, photo + Taken on #{I18n.l photo.date_taken} diff --git a/app/views/photos/_item_photos.haml b/app/views/photos/_item_photos.haml deleted file mode 100644 index 05a2f8319..000000000 --- a/app/views/photos/_item_photos.haml +++ /dev/null @@ -1,11 +0,0 @@ -%h2 Photos - -- if photos.size.positive? || (can?(:edit, item) && can?(:create, Photo)) - - if photos.size.positive? - = page_entries_info photos - = will_paginate photos - .row - - photos.each do |photo| - .col-xs-6.col-md-3.six-across= render 'photos/thumbnail', photo: photo - -= add_photo_button(item) diff --git a/app/views/photos/_likes.html.haml b/app/views/photos/_likes.html.haml index 47cca8634..fb0b0e918 100644 --- a/app/views/photos/_likes.html.haml +++ b/app/views/photos/_likes.html.haml @@ -1,13 +1,14 @@ -- if member_signed_in? - - if can?(:new, Like) && !photo.liked_by?(current_member) - = link_to likes_path(photo_id: photo.id, format: :json), - method: :post, remote: true, class: 'photo-like like-btn' do - = render 'likes/count', likeable: photo - - else - - like = photo.likes.find_by(member: current_member) - - if like && can?(:destroy, like) - = link_to like_path(id: like.id, format: :json), - method: :delete, remote: true, class: 'photo-like like-btn' do +%span.likes + - if member_signed_in? + - if can?(:new, Like) && !photo.liked_by?(current_member) + = link_to likes_path(photo_id: photo.id, format: :json), + method: :post, remote: true, class: 'photo-like like-btn' do = render 'likes/count', likeable: photo -- else - = render 'likes/count', likeable: photo \ No newline at end of file + - else + - like = photo.likes.find_by(member: current_member) + - if like && can?(:destroy, like) + = link_to like_path(id: like.id, format: :json), + method: :delete, remote: true, class: 'photo-like like-btn' do + = render 'likes/count', likeable: photo + - else + = render 'likes/count', likeable: photo \ No newline at end of file diff --git a/app/views/photos/_photo.haml b/app/views/photos/_photo.haml index 341a8ec7f..bcd42f045 100644 --- a/app/views/photos/_photo.haml +++ b/app/views/photos/_photo.haml @@ -1,4 +1,4 @@ -.planting-full-photo +.photo = image_tag(photo.fullsize_url, alt: photo.title, class: 'img img-responsive') .text %p diff --git a/app/views/photos/_tiny.html.haml b/app/views/photos/_tiny.html.haml new file mode 100644 index 000000000..3e95fb558 --- /dev/null +++ b/app/views/photos/_tiny.html.haml @@ -0,0 +1,3 @@ +- if photo.present? + .photo + = image_tag(photo.thumbnail_url, alt: photo.title, class: 'img img-tiny') diff --git a/app/views/photos/index.html.haml b/app/views/photos/index.html.haml index 1f9896377..baef72745 100644 --- a/app/views/photos/index.html.haml +++ b/app/views/photos/index.html.haml @@ -8,21 +8,13 @@ - content_for :breadcrumbs do %li.breadcrumb-item= link_to 'Photos', photos_path -.pagination - = page_entries_info @photos - = will_paginate @photos -.row += page_entries_info @photos += will_paginate @photos + +.index-cards - @photos.each do |p| - .col-md-2.six-across - = render 'photos/card', photo: p - -# .thumbnail{ style: 'height: 220px' } - -# = link_to image_tag(p.thumbnail_url, alt: p.title, class: 'img'), p - -# %p - -# = link_to p.title, p - -# by - -# = link_to p.owner, p.owner + = render 'photos/card', photo: p -.pagination - = page_entries_info @photos - = will_paginate @photos += page_entries_info @photos += will_paginate @photos diff --git a/app/views/photos/new.html.haml b/app/views/photos/new.html.haml index 9daefaac3..e60e4fd78 100644 --- a/app/views/photos/new.html.haml +++ b/app/views/photos/new.html.haml @@ -30,12 +30,9 @@ - @photos.each do |photo| .col-md-2.col-6 .card - = link_to image_tag(FlickRaw.url_z(photo), alt: '', class: 'img img-card', - width: 150, height: 150), - photos_path(photo: { flickr_photo_id: photo.id }, type: @type, id: @id), - method: :post - .card-body - %p.photo-title= photo.title + = link_to photos_path(photo: { source_id: photo.id, source: 'flickr' }, id: @id, type: @type ), method: :post do + = image_tag(FlickRaw.url_z(photo), alt: photo.title, class: 'img img-card') + .card-body= photo.title .row.pagination .col-md-12= will_paginate @photos diff --git a/app/views/photos/show.html.haml b/app/views/photos/show.html.haml index ea309fe42..c286d8afc 100644 --- a/app/views/photos/show.html.haml +++ b/app/views/photos/show.html.haml @@ -12,27 +12,33 @@ %li.breadcrumb-item.active= link_to @photo, @photo .row{id: "photo-#{@photo.id}"} - .col-md-8 - %h1.text-center.ellipsis=@photo.title - %p.text-center - = image_tag(@photo.fullsize_url, alt: @photo.title, class: 'rounded img-fluid shadow-sm') - .col-md-4 + .col-md-9 + %section.photo + %h2.text-center.ellipsis + = photo_icon + = @photo.title - %p - = render 'photos/actions', photo: @photo - = link_to "View on Flickr", @photo.link_url, class: 'btn' - %span.btn= render 'photos/likes', photo: @photo + %p.text-center + = image_tag(@photo.fullsize_url, alt: @photo.title, class: 'rounded img-responsive shadow-sm') - if @crops.size.positive? .index-cards - @crops.each do |crop| = render 'crops/thumbnail', crop: crop + .col-md-3 %p - = photo_icon - %strong Photo by - = link_to @photo.owner, @photo.owner - Taken on #{@photo.date_taken} + = render 'photos/actions', photo: @photo + = link_to @photo.link_url, class: 'btn btn-info' do + - if @photo.source == 'flickr' + = icon 'fab', 'flickr' + View on #{@photo.source.titleize} + %span.btn= render 'photos/likes', photo: @photo + - if @photo.date_taken.present? + %h3 Taken on #{I18n.l @photo.date_taken.to_date} + + = render @photo.owner + %p %strong License: - if @photo.license_url @@ -40,5 +46,4 @@ - else = @photo.license_name - - if @photo.associations? - = render "associations", photo: @photo \ No newline at end of file + = render "associations", photo: @photo \ No newline at end of file diff --git a/app/views/places/index.html.haml b/app/views/places/index.html.haml index f8537827a..ebc180a81 100644 --- a/app/views/places/index.html.haml +++ b/app/views/places/index.html.haml @@ -1,4 +1,3 @@ - content_for :title, t(".title", site_name: ENV['GROWSTUFF_SITE_NAME']) = render partial: 'search_form' -#placesmap - +#placesmap.map diff --git a/app/views/places/show.html.haml b/app/views/places/show.html.haml index 23fa46e3e..98b9b7360 100644 --- a/app/views/places/show.html.haml +++ b/app/views/places/show.html.haml @@ -9,46 +9,44 @@ %h1 #{ENV['GROWSTUFF_SITE_NAME']} community near #{@place} = render partial: 'search_form' -#placesmap{ style: "height:300px" } +%section.map#placesmap{ style: "height:300px" } -%h3#members= "Nearby members" +%section.members + %h2#members= "Nearby members" -- if !@nearby_members.empty? - .row - - @nearby_members.first(30).each do |member| - .col-md-4.three-across + - if @nearby_members.any? + .index-cards.members + - @nearby_members.first(30).each do |member| = render partial: "members/thumbnail", locals: { member: member } + - else + %p No nearby members = link_to "View all members >>", members_path - %h3#seeds Seeds available for trade near #{@place} +%section.seeds + %h2#seeds Seeds available for trade near #{@place} - crop_id = [] - @nearby_members.first(10).each do |member| - member.seeds.first(5).each do |seed| - crop_id.push seed.crop.id - - if !crop_id.blank? - .row + - if crop_id.present? + .index-cards.crops - crop_id.uniq.first(20).each do |crop| - .col-md-2.six-across - = render partial: "crops/thumbnail", locals: { crop: Crop.find(crop) } - = link_to "View all seeds >>", seeds_path + = render partial: "crops/thumbnail", locals: { crop: Crop.find(crop) } - else %p No nearby seeds found + = link_to "View all seeds >>", seeds_path - #plantings - %h3 Recent plantings near #{@place} - +%section#plantings + %h2 Recent plantings near #{@place} - plantings = [] - @nearby_members.first(10).each do |member| - member.plantings.first(5).each do |planting| - plantings << planting - - if !plantings.blank? - .row + - if plantings.any? + .index-cards.plantings - plantings.first(10).each.with_index do |planting, index| - .col-xs-12.col-lg-6 - = render partial: "plantings/card", locals: { planting: planting, index: index } - = link_to "View all plantings >>", plantings_path + = render partial: "plantings/card", locals: { planting: planting, index: index } - else %p No nearby plantings found -- else - %p No results found + = link_to "View all plantings >>", plantings_path diff --git a/app/views/plant_parts/index.html.haml b/app/views/plant_parts/index.html.haml index 34846e9c3..22c4784da 100644 --- a/app/views/plant_parts/index.html.haml +++ b/app/views/plant_parts/index.html.haml @@ -1,21 +1,25 @@ - content_for :title, "Plant parts" +%h1 Plant Parts + - if can? :create, PlantPart - = link_to 'New Plant part', new_plant_part_path + = link_to 'New Plant part', new_plant_part_path, class: 'btn btn-info' -- @plant_parts.each do |plant_part| - %h2= plant_part - %p - - if plant_part.crops.empty? - No crops are harvested for this plant part (yet). - - else - - plant_part.crops.limit(100).each do |crop| - = link_to(crop, crop_path(crop)) - %p - = link_to "More detail", plant_part - - %p - - if can? :edit, plant_part - = link_to 'Edit', edit_plant_part_path(plant_part), class: 'btn btn-default btn-xs' - - if can? :destroy, plant_part - = link_to 'Delete', plant_part, method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-default btn-xs' +.index-cards + - @plant_parts.each do |plant_part| + .card.plant-part + .card-header + %h2= link_to plant_part, plant_part + .card-body + %p + - if plant_part.crops.empty? + No crops are harvested for this plant part (yet). + - else + - plant_part.crops.limit(20).each do |crop| + = render 'crops/tiny', crop: crop + .card-footer + %p + - if can? :edit, plant_part + = link_to 'Edit', edit_plant_part_path(plant_part), class: 'btn btn-default btn-xs' + - if can? :destroy, plant_part + = link_to 'Delete', plant_part, method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-default btn-xs' diff --git a/app/views/plantings/_actions.html.haml b/app/views/plantings/_actions.html.haml index f69b900b9..e974526af 100644 --- a/app/views/plantings/_actions.html.haml +++ b/app/views/plantings/_actions.html.haml @@ -1,6 +1,6 @@ - if can?(:edit, planting) - .dropdown.float-right.planting-actions - %a#planting-actions-button.btn.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", type: "button", href: '#'} Actions + .dropdown.planting-actions + %a#planting-actions-button.btn.btn-info.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", type: "button", href: '#'} Actions .dropdown-menu.dropdown-menu-xs{"aria-labelledby" => "planting-actions-button"} = planting_edit_button(planting, classes: 'dropdown-item') = add_photo_button(planting, classes: 'dropdown-item') diff --git a/app/views/plantings/_badges.html.haml b/app/views/plantings/_badges.html.haml index fd38ccb57..8402aaf36 100644 --- a/app/views/plantings/_badges.html.haml +++ b/app/views/plantings/_badges.html.haml @@ -7,18 +7,12 @@ = planting_finish_button(planting) - elsif planting.late? %span.badge.badge-info.badge-late= t('.late_finishing') - - else - %span.badge.badge-info{'data-toggle': "tooltip", 'data-placement': "top", title: 'Predicted days until planting is finished'} - = finished_icon - = t('label.days_until_finished', number: days_from_now_to_finished(planting)) // Harvest times - unless planting.super_late? - if planting.harvest_time? %span.badge.badge-info.badge-harvest{'data-toggle': "tooltip", 'data-placement': "top", title: 'Planting is ready for harvesting now'} - = harvest_icon = t('label.harvesting_now') - elsif planting.before_harvest_time? - %span.badge.badge-info{'data-toggle': "tooltip", 'data-placement': "top", title: 'Predicted days until harvest'} - = harvest_icon - = t('label.days_until_harvest', number: days_from_now_to_first_harvest(planting)) + %span.badge.badge-info{'data-toggle': "tooltip", 'data-placement': "top", title: 'Predicted weeks until harvest'} + = t('label.weeks_until_harvest', number: in_weeks(days_from_now_to_first_harvest(planting))) diff --git a/app/views/plantings/_card.html.haml b/app/views/plantings/_card.html.haml index c6695f3e0..2f9ffc6d9 100644 --- a/app/views/plantings/_card.html.haml +++ b/app/views/plantings/_card.html.haml @@ -1,6 +1,6 @@ -.card.planting +.card.planting{class: planting.active? ? '' : 'card-finished'} = link_to planting do - = image_tag planting_image_path(planting, full_size: true), class: 'img-card', alt: planting + = image_tag planting_image_path(planting), class: 'img-card', alt: planting - if can? :edit, planting .planting-quick-actions .dropdown @@ -17,8 +17,8 @@ - if can? :destroy, planting .dropdown-divider = delete_button(planting, classes: 'dropdown-item text-danger') - - .card-body.text-center - %h4.card-title= link_to planting.crop, planting - .text-center= render 'plantings/badges', planting: planting - = render 'plantings/progress', planting: planting \ No newline at end of file + = link_to planting do + .card-body.text-center + %h4= planting.crop + .text-center= render 'plantings/badges', planting: planting + = render 'plantings/progress', planting: planting diff --git a/app/views/plantings/_descendants.html.haml b/app/views/plantings/_descendants.html.haml index 4cfc1e23e..2dd31b8d0 100644 --- a/app/views/plantings/_descendants.html.haml +++ b/app/views/plantings/_descendants.html.haml @@ -1,9 +1,17 @@ %h2 Seeds saved +%a.btn.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", role: "button"} + = seed_icon + = t('buttons.save_seeds') +.dropdown-menu.dropdown-secondary + - Seed::TRADABLE_TO_VALUES.each do |trade| + = link_to seeds_path(return: 'planting', seed: {crop_id: planting.crop.id, parent_planting_id: planting.id, tradable_to: trade}), method: :post, class: 'dropdown-item' do + Will trade: + = trade + - if planting.child_seeds.size.positive? - - planting.child_seeds.each do |seed| - = render 'seeds/thumbnail', seed: seed + .index-cards + - planting.child_seeds.each do |seed| + = render 'seeds/card', seed: seed - else %p No seeds saved - -= planting_save_seeds_button(planting) diff --git a/app/views/plantings/_facts.haml b/app/views/plantings/_facts.haml index 480595bde..23ade1d2f 100644 --- a/app/views/plantings/_facts.haml +++ b/app/views/plantings/_facts.haml @@ -1,106 +1,78 @@ -.index-cards.planting-facts +.index-cards.facts.plantingfacts - if planting.parent_seed - .card.planting-fact-card + .card.fact-card %h3 Parent seed - %strong - = link_to seed_path(planting.parent_seed) do - = seed_icon - %span=planting.parent_seed + = render 'seeds/thumbnail', seed: planting.parent_seed + .card.fact-card{class: planting.planted_at.present? ? '' : 'text-muted'} + %h3 + Planted + = editable :date, planting, :planted_at, display_field: '.planted_at' + %strong.plantingfact--weekssinceplanted.planted_at + - if planting.planted_at.present? + = I18n.t('date.abbr_month_names')[planting.planted_at.month] + - else + unknown + - if planting.planted_at.present? + %span.planted_at + =planting.planted_at.year - - if planting.finished - .card.planting-fact-card - %h3 Planted - %strong=planting_icon - - if planting.quantity.present? - %span= planting.quantity - - - if planting.planted_at.present? && !planting.finished? - .card.planting-fact-card - %h3 Planted - %strong #{planting.days_since_planted} - %span days ago - - if planting.quantity.to_i.positive? - .card.planting-fact-card - %h3 Quantity - %strong= planting.quantity - %span - - if planting.planted_from.present? - #{pluralize((planting.quantity.to_i), planting.planted_from)} + .card.fact-card{class: planting.quantity.present? ? '' : 'text-muted'} + %h3 + Quantity + %small= editable :text_field, planting, :quantity, display_field: '.plantingfact--quantity' + %strong.plantingfact--quantity + - if planting.quantity.to_i.positive? + = planting.quantity + -else + unknown + %span + - if planting.quantity.to_i.positive? && planting.planted_from.present? + = planting.planted_from.pluralize(planting.quantity.to_i) - unless planting.finished? - .card.planting-fact-card.grid-sizer + .card.fact-card.grid-sizer %h3 Growing %strong= seedling_icon - - if planting.planted_at.present? - %span= planting.planted_at.to_formatted_s(:rfc822) + %span + Planting is still growing today - - if planting.percentage_grown - .card.planting-fact-card - %h3 Progress - %strong #{sprintf '%.0f', planting.percentage_grown}% - .progress - .progress-bar.progress-bar-success{"aria-valuemax" => "100", "aria-valuemin" => "0", "aria-valuenow" => planting.percentage_grown, :role => "progressbar", :style => "width: #{planting.percentage_grown}%"} - %span.sr-only #{sprintf '%.0f', planting.percentage_grown}% - - - - - - if planting.planted_from.present? - .card.planting-fact-card - %h3 Grown from - %span=planting.planted_from - - .card.planting-fact-card - %h3 Grown in - %strong= sunniness_icon(planting.sunniness) - %span= planting.sunniness.blank? ? "not specified" : planting.sunniness - - -# .card.planting-fact-card - -# %h3 Garden - -# = link_to planting.garden do - -# - if planting.garden.default_photo.present? - -# = image_tag planting.garden.default_photo.thumbnail_url - -# - else - -# %strong= garden_icon - -# %span= planting.garden.name - - .card.planting-fact-card + .card.fact-card{class: planting.planted_from.present? ? '' : 'text-muted'} %h3 - = planting.finished? ? "Harvests" : "Harvesting" - %strong - = link_to planting_harvests_path(planting) do - = harvest_icon - %span= planting.first_harvest_date&.to_formatted_s(:rfc822) || planting&.first_harvest_predicted_at&.to_formatted_s(:rfc822) || 'unknown' + Grown from + = editable :select, planting, :planted_from, collection: Planting::PLANTED_FROM_VALUES, display_field: '.plantingfact--plantedfrom' + %strong.plantingfact--plantedfrom + = planting.planted_from.present? ? planting.planted_from : 'unknown' + + .card.fact-card{class: planting.sunniness.present? ? '' : 'text-muted'} + %h3 + Grown in + = editable :select, planting, :sunniness, collection: Planting::SUNNINESS_VALUES, display_field: '.plantingfact--sunniness' + %strong= sunniness_icon(planting.sunniness) + %span.plantingfact--sunniness + = planting.sunniness.blank? ? "not specified" : planting.sunniness - if planting.crop.perennial - .card.planting-fact-card - %h3 Perennial + .card.fact-card + %h3.plantingfact--perennial Perennial %strong=perennial_icon - - else - - if !planting.finished? && planting.finish_is_predicatable? - .card.planting-fact-card - - days = days_from_now_to_finished(planting) - - if days.positive? - %h3 Prediction - %strong #{days} - %span days until finished - - else - %h3 Finish - %strong #{days * -1} - %span days ago - %span= planting.finish_predicted_at&.to_formatted_s(:rfc822) - if planting.child_seeds.size.positive? - .card.planting-fact-card + .card.fact-card %h3 Seeds saved %strong = link_to planting_seeds_path(planting) do = seed_icon - %span #{pluralize(planting.child_seeds.size, 'packet')} of seed + %span.plantingfact--seedssaved #{pluralize(planting.child_seeds.size, 'packet')} of seed - if planting.finished? - .card.planting-fact-card + .card.fact-card %h3 Finished - %strong=finished_icon - %span=planting.finished_at&.to_formatted_s(:rfc822) + %strong + - if planting.finished_at.present? + = I18n.t('date.abbr_month_names')[planting.finished_at.month] + - else + unknown date + - if planting.finished_at.present? + %span.plantingfact--finish + = planting.finished_at.year diff --git a/app/views/plantings/_harvests.html.haml b/app/views/plantings/_harvests.html.haml index 5ed44f59f..aa3a0fd40 100644 --- a/app/views/plantings/_harvests.html.haml +++ b/app/views/plantings/_harvests.html.haml @@ -1,11 +1,20 @@ %h2 Harvests + +- if can? :edit, @planting + %a.btn.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", :href => "#", :role => "button"} + = harvest_icon + Record harvest + + .dropdown-menu.dropdown-secondary + - PlantPart.all.each do |plant_part| + = link_to harvests_path(return: 'planting', harvest: {crop_id: @planting.crop_id, planting_id: @planting.id, plant_part_id: plant_part.id}), method: :post, class: 'dropdown-item' do + = plant_part.name + - if planting.harvests.empty? %p No harvests recorded - if !planting.finished? && can?(:edit, planting) && can?(:create, Harvest) %p Record your harvests here to improve crop predictions, and you'll be able to compare with your garden next season. - else - .row + .index-cards - planting.harvests.order(created_at: :desc).includes(:crop).each do |harvest| - .col-md-2.col-sm-6= render 'harvests/thumbnail', harvest: harvest - -= planting_harvest_button(planting) + = render 'harvests/thumbnail', harvest: harvest diff --git a/app/views/plantings/_owner.haml b/app/views/plantings/_owner.haml index b1a57a937..89bfcae4d 100644 --- a/app/views/plantings/_owner.haml +++ b/app/views/plantings/_owner.haml @@ -1,17 +1,18 @@ -.well - .row - .col-md-6 - %h4 - Planted by - = link_to @planting.owner, @planting.owner - = link_to "view all #{@planting.owner}'s plantings", member_gardens_path(@planting.owner) +.card + .card-body + .row + .col + %h4 + Planted by + = link_to @planting.owner, @planting.owner + = link_to "view all #{@planting.owner}'s plantings", member_gardens_path(@planting.owner) - %p - Planted in - = link_to @planting.garden, @planting.garden - - if @planting.owner.location %p - %small - View other plantings, members and more near - = link_to @planting.owner.location, place_path(@planting.owner.location, anchor: "plantings") - .col-md-6= render "members/avatar", member: @planting.owner \ No newline at end of file + Planted in + = link_to @planting.garden, @planting.garden + - if @planting.owner.location + %p + %small + View other plantings, members and more near + = link_to @planting.owner.location, place_path(@planting.owner.location, anchor: "plantings") + .col= render "members/avatar", member: @planting.owner \ No newline at end of file diff --git a/app/views/plantings/_photos.haml b/app/views/plantings/_photos.haml index f27f2864d..800b42828 100644 --- a/app/views/plantings/_photos.haml +++ b/app/views/plantings/_photos.haml @@ -4,7 +4,9 @@ - if can?(:edit, planting) && can?(:create, Photo) %p.text-right= add_photo_button(planting) %p.text-right - = link_to 'more photos', planting_photos_path(planting), class: 'btn' + = link_to planting_photos_path(planting), class: 'btn' do + = photo_icon + more photos - else %p No photos. - if can?(:edit, planting) && can?(:create, Photo) diff --git a/app/views/plantings/_progress.html.haml b/app/views/plantings/_progress.html.haml index 16fb5686f..a61233f3e 100644 --- a/app/views/plantings/_progress.html.haml +++ b/app/views/plantings/_progress.html.haml @@ -1,8 +1,11 @@ -- if planting.annual? && planting.percentage_grown.present? +- if planting.active? && planting.annual? && planting.percentage_grown.present? .progress - .progress-bar.bg-success{"aria-valuemax" => "100", "aria-valuemin" => "0", "aria-valuenow" => planting.percentage_grown, :role => "progressbar", :style => "width: #{planting.percentage_grown}%; height: 25px"} + .progress-bar.bg-success{"aria-valuemax" => "100", "aria-valuemin" => "0", "aria-valuenow" => planting.percentage_grown, role: "progressbar", style: "width: #{planting.percentage_grown}%"} .float-left #{sprintf '%.0f', planting.percentage_grown}% -- unless planting.finish_predicted_at.blank? - .float-right= planting.finish_predicted_at.strftime(Date::DATE_FORMATS[:ymd]) + - unless planting.finish_predicted_at.blank? + .float-right + -# = planting.finish_predicted_at.strftime(Date::DATE_FORMATS[:ymd]) + = I18n.t('date.abbr_month_names')[planting.finish_predicted_at.month] + = planting.finish_predicted_at.year diff --git a/app/views/plantings/_progress_list.haml b/app/views/plantings/_progress_list.haml new file mode 100644 index 000000000..b56320ed8 --- /dev/null +++ b/app/views/plantings/_progress_list.haml @@ -0,0 +1,15 @@ +- plantings.includes(:crop).annual.order(:planted_at).each_with_index do |planting, i| + - if i.positive? + %hr/ + .row.progress-row + .col-12.col-md-4.progress-row--crop + = render 'plantings/tiny', planting: planting + = render 'plantings/badges', planting: planting + .col-12.col-md-6.progress-row--bar + - if planting.planted_at.blank? + %small set "planted" date to allow predictions + - elsif planting.percentage_grown.blank? + %small not enough data on #{planting.crop} to predict + - else + = render 'plantings/progress', planting: planting + .col-12.col-md-2= render 'plantings/quick_actions', planting: planting diff --git a/app/views/plantings/_quick_actions.haml b/app/views/plantings/_quick_actions.haml index a2e4c6fac..61d173ef2 100644 --- a/app/views/plantings/_quick_actions.haml +++ b/app/views/plantings/_quick_actions.haml @@ -1,16 +1,14 @@ - if can?(:edit, planting) - .planting-quick-actions.text-right - %a.btn#actionsMenu.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", href: "#"} - -# =icon('fas', 'ellipsis-v') - %ul.dropdown-menu.dropdown-menu-left{"aria-labelledby" => "actionsMenu"} - %li.btn-xs - = link_to t('view'), planting, class: 'btn' - %li.btn-xs= planting_edit_button(planting) - %li.btn-xs= add_photo_button(planting) + %a#planting-actions-menu.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", href: "#"} + /=icon('fas', 'ellipsis-v') + %ul.dropdown-menu.dropdown-menu-left{"aria-labelledby" => "planting-actions-menu"} + %li= link_to t('view'), planting, class: 'dropdown-item' + %li= planting_edit_button(planting, classes: 'dropdown-item') + %li= add_photo_button(planting, classes: 'dropdown-item') - - if planting.active? - %li.btn-xs= planting_finish_button(planting) - %li.btn-xs= planting_harvest_button(planting) - %li.btn-xs= planting_save_seeds_button(planting) + - if planting.active? + %li= planting_finish_button(planting, classes: 'dropdown-item') + %li= planting_harvest_button(planting, classes: 'dropdown-item') + %li= planting_save_seeds_button(planting, classes: 'dropdown-item') diff --git a/app/views/plantings/_thumbnail.html.haml b/app/views/plantings/_thumbnail.html.haml index c8513fd7e..60bf071cf 100644 --- a/app/views/plantings/_thumbnail.html.haml +++ b/app/views/plantings/_thumbnail.html.haml @@ -1,10 +1,11 @@ .card.thumbnail - /= render 'plantings/quick_actions', planting: planting .planting-thumbnail-image - = link_to image_tag(planting_image_path(planting, full_size: true), + = link_to image_tag(planting_image_path(planting), alt: planting.crop, class: 'img-card'), planting .card-body - %h5.card-title= link_to planting.crop, planting + %h5.card-title + = crop_icon(planting.crop) + = link_to planting.crop, planting %h6.card-subtitle.text-muted=planting.finished_at = render 'plantings/badges', planting: planting diff --git a/app/views/plantings/_timeline.html.haml b/app/views/plantings/_timeline.html.haml new file mode 100644 index 000000000..925456c9b --- /dev/null +++ b/app/views/plantings/_timeline.html.haml @@ -0,0 +1,23 @@ +- if @planting.crop.annual? + .d-flex.justify-content-between + - if @planting.planted_at.present? + %p.small #{ image_icon 'planting-hand'} Planted #{I18n.l @planting.planted_at} + - if @planting.first_harvest_date.present? + %p.small #{harvest_icon} Harvest started #{I18n.l @planting.first_harvest_date} + - elsif @planting.first_harvest_predicted_at.present? + %p.small #{harvest_icon} First harvest expected #{I18n.l @planting.first_harvest_predicted_at} + - if @planting.finished_at.present? + %p.small #{finished_icon} Finished #{I18n.l @planting.finished_at} + - elsif @planting.finish_predicted_at.present? + %p.small #{finished_icon} Finish expected #{I18n.l @planting.finish_predicted_at} + - if @planting.planted_at.present? && @planting.expected_lifespan.present? + .progress + .progress-bar{"aria-valuemax" => "100", "aria-valuemin" => "0", "aria-valuenow" => @planting.percentage_grown, role: "progressbar", style: "width: #{@planting.percentage_grown}%"} + %ul.list-unstyled.d-flex.justify-content-between + - in_weeks(@planting.expected_lifespan).times do |week_number| + %li{class: @planting.planted_at + week_number.weeks > Time.zone.today ? 'text-muted progress-fade' : '', 'data-toggle': "tooltip", 'data-placement': "top", title: I18n.l(@planting.planted_at + week_number.weeks)} + = render 'timeline_icon', + planting: @planting, + week_number: week_number, + date_this_week: @planting.planted_at + week_number.weeks + (One emojii = 1 week) diff --git a/app/views/plantings/_timeline_icon.haml b/app/views/plantings/_timeline_icon.haml new file mode 100644 index 000000000..9e8a22625 --- /dev/null +++ b/app/views/plantings/_timeline_icon.haml @@ -0,0 +1,14 @@ +- if @planting.first_harvest_date.present? + - # this planting has harvests + - if date_this_week >= @planting.first_harvest_date + = crop_icon(@planting.crop) + - else + = growing_icon +- elsif @planting.first_harvest_predicted_at.present? + - # no harvests yet, so use predicted harvest + - if date_this_week < @planting.first_harvest_predicted_at + = growing_icon + - elsif date_this_week > @planting.first_harvest_predicted_at + = crop_icon(@planting.crop) +- else + = week_number \ No newline at end of file diff --git a/app/views/plantings/_tiny.html.haml b/app/views/plantings/_tiny.html.haml new file mode 100644 index 000000000..ccffd765a --- /dev/null +++ b/app/views/plantings/_tiny.html.haml @@ -0,0 +1,4 @@ += link_to planting do + .chip.crop-chip + = crop_icon(planting.crop) + = planting.crop.name diff --git a/app/views/plantings/index.html.haml b/app/views/plantings/index.html.haml index f0c60a53e..639840fb3 100644 --- a/app/views/plantings/index.html.haml +++ b/app/views/plantings/index.html.haml @@ -1,11 +1,5 @@ - content_for :title, title('plantings', @owner, @crop, @planting) -%h1 - = planting_icon - = title('plantings', @owner, @crop, @planting) - -= render 'layouts/nav', model: Planting - - content_for :breadcrumbs do - if @owner %li.breadcrumb-item= link_to 'Plantings', plantings_path @@ -13,28 +7,39 @@ - else %li.breadcrumb-item.active= link_to 'Plantings', plantings_path -%section.border-top - .btn-group - = link_to plantings_active_tickbox_path(@owner, @show_all), class: 'btn' do - = check_box_tag 'active', 'all', @show_all - include in-active +%h1 + = planting_icon + = title('plantings', @owner, @crop, @planting) - - if @owner - = link_to t('.view_owners_profile', owner: @owner), member_path(@owner), class: 'btn' +.row + .col-md-6 + %ul.nav + %li.list-group-item + = link_to show_inactive_tickbox_path('plantings', owner: @owner, show_all: @show_all) do + = check_box_tag 'active', 'all', @show_all + include finished + - if @owner + %li.list-group-item.btn + = link_to t('.view_owners_profile', owner: @owner), member_path(@owner) + .col-md-6= render 'layouts/nav', model: Planting .row .col-12= page_entries_info @plantings - .col-12= render 'layouts/pagination', collection: @plantings + .col-12= will_paginate @plantings -.index-cards= render @plantings, full: true +.index-cards + - @plantings.each do |planting| + = render planting, full: true -.row - .col-12= page_entries_info @plantings - .col-12= render 'layouts/pagination', collection: @plantings += will_paginate @plantings -%p= t('.the_data_on_this_page_is_available_in_the_following_formats') -%ul.list-group.list-group-horizontal - - ['csv', 'json', 'rss'].each do |format| - %li.list-group-item - = icon 'fas', format - = link_to format.upcase, (@owner ? member_plantings_path(@owner, format: format) : plantings_path(format: format)) +%hr/ + +%section.open-data + %h3= t('label.data') + %ul.nav#open-data + - ['csv', 'json', 'rss'].each do |format| + %li.list-group-item + = link_to (@owner ? member_plantings_path(@owner, format: format) : plantings_path(format: format)) do + = icon 'fas', format.to_s + = format.upcase diff --git a/app/views/plantings/show.html.haml b/app/views/plantings/show.html.haml index 2554a9421..0ff106ccc 100644 --- a/app/views/plantings/show.html.haml +++ b/app/views/plantings/show.html.haml @@ -9,33 +9,59 @@ = tag("meta", property: "og:url", content: request.original_url) = tag("meta", property: "og:site_name", content: ENV['GROWSTUFF_SITE_NAME']) +- content_for :breadcrumbs do + %li.breadcrumb-item= link_to 'Plantings', plantings_path + %li.breadcrumb-item= link_to @planting.owner, member_plantings_path(@planting.owner) + %li.breadcrumb-item.active= link_to @planting.crop.name, @planting .planting .row .col-md-8.col-xs-12 - %h1 - = planting_icon - %strong= @planting - = render 'plantings/actions', planting: @planting + .jumbotron + .d-flex.justify-content-between + %h1.display-3 + = crop_icon(@planting.crop) + %strong= @planting.crop.name.titleize + %small.text-muted= @planting.crop.default_scientific_name + - if @planting.finished? + Finished + - elsif @planting.percentage_grown.present? + #{@planting.percentage_grown.to_i}% + = render 'timeline', planting: @planting + + - if @planting.parent_seed.nil? && @matching_seeds && @matching_seeds.any? && @planting.owner == current_member + .alert.alert-info{role: "alert"} + = bootstrap_form_for(@planting) do |f| + Is this from one of these plantings? + - @matching_seeds.each do |seed| + = f.radio_button :parent_seed_id, seed.id, label: seed + = f.submit "save", class: 'btn btn-sm' + + .col-md-4.col-xs-12 + = render 'plantings/owner', planting: @planting + + .col-md-8.col-xs-12 %section= render 'facts', planting: @planting - if @planting.description.present? - %hr/ + = cute_icon .card .card-header %h2 Notes .card-body :growstuff_markdown #{strip_tags(@planting.description)} - %hr/ %section= render 'plantings/photos', photos: @photos, planting: @planting - .col-md-4.col-xs-12.text-right - .card - .card-body= render 'plantings/owner', planting: @planting + .col-md-4.col-xs-12 + = render 'plantings/actions', planting: @planting + %hr/ = render @planting.crop .row - .col-md-12 - %hr/ - = render 'plantings/harvests', planting: @planting - %hr/ - = render 'plantings/descendants', planting: @planting + .col-md-6 + %section.harvests + %a{name: 'harvests'} + = render 'plantings/harvests', planting: @planting + .col-md-6 + %section.descendants + %a{name: 'seeds'} + = render 'plantings/descendants', planting: @planting diff --git a/app/views/posts/_preview.haml b/app/views/posts/_preview.haml index 1d7ccbf37..275e5f1fa 100644 --- a/app/views/posts/_preview.haml +++ b/app/views/posts/_preview.haml @@ -1,14 +1,6 @@ -%section - .row - .col-md-12 - .card.card-cascade - .card-header - %h2= link_to post.subject, post - %p - Written by - %strong= link_to post.author, post.author - .card-body - .float-right= render 'members/tiny', member: post.author - - %p - = truncate(strip_tags(post.body), length: 200) +.card.card-cascade + .card-header + %h3= link_to post.subject, post + = render 'members/tiny', member: post.author + .card-body + %p= truncate(strip_tags(post.body), length: 200) diff --git a/app/views/posts/_single.html.haml b/app/views/posts/_single.html.haml index 0057e7367..10a372b6d 100644 --- a/app/views/posts/_single.html.haml +++ b/app/views/posts/_single.html.haml @@ -17,32 +17,3 @@ :growstuff_markdown #{ strip_tags @post.body } - --# .card --# .post{ id: "post-#{post.id}" } --# .row --# .col-md-1 --# = render partial: "members/avatar", locals: { member: post.author } --# .col-md-11 --# - if defined?(subject) --# %h3= link_to strip_tags(post.subject), post - --# .post-meta --# %p --# Posted by --# - if post.author --# = link_to post.author.login_name, member_path(post.author) --# - else --# Member Deleted --# - if post.forum --# in --# = link_to post.forum, post.forum --# on --# = post.created_at --# - if post.updated_at > post.created_at --# and edited at --# = post.updated_at - --# .post-body --# :growstuff_markdown --# #{ strip_tags post.body } diff --git a/app/views/posts/index.html.haml b/app/views/posts/index.html.haml index fd987aeac..9e1ffc35b 100644 --- a/app/views/posts/index.html.haml +++ b/app/views/posts/index.html.haml @@ -1,47 +1,47 @@ - content_for :title, @author ? t('.title.author_posts', author: @author) : t('.title.default') - content_for :breadcrumbs do + %li.breadcrumb-item= link_to 'Posts', posts_path - if @author.present? %li.breadcrumb-item= link_to @author, @author %li.breadcrumb-item.active= link_to 'posts', posts_path(author: @author.slug) %h1= @author ? t('.title.author_posts', author: @author) : t('.title.default') -.pagination= render 'layouts/pagination', collection: @posts -.row - .card-deck - - @posts.each do |post| - .col-12.col-md-6 - .card.post - .card-body - .row - .col-2.text-right - = render 'members/avatar', member: post.author - .col-10.border-left - %h5.card-title - = link_to strip_tags(post.subject), post - - if post.comments.size.positive? - %span.badge.badge-pill.badge-info.float-right - = icon 'fas', 'comment' - = post.comments.size - %p.text-muted - Posted by - - if post.author - = link_to post.author.login_name, member_path(post.author) - - else - Member Deleted - - if post.forum - in #{link_to post.forum, post.forum} - on #{post.created_at}" - - if post.updated_at > post.created_at - and edited at #{post.updated_at} - .card-text - .post-body= display_post_truncated(post) - - post.crops.each do |crop| - %span.badge.badge-pill.badge-primary= link_to crop, crop += render 'layouts/nav', model: Post + += will_paginate @posts + +- @posts.each do |post| + .card.post + .card-body + %h5.card-title + = link_to strip_tags(post.subject), post + - if post.comments.size.positive? + %span.badge.badge-pill.badge-info.float-right + = icon 'fas', 'comment' + + = post.comments.size + %p.text-muted + Posted by + - if post.author + = link_to post.author.login_name, member_path(post.author) + - else + Member Deleted + + - if post.forum + in #{link_to post.forum, post.forum} + on #{post.created_at}" + - if post.updated_at > post.created_at + and edited at #{post.updated_at} + = render 'members/tiny', member: post.author + .card-body + .post-body= display_post_truncated(post) + - post.crops.each do |crop| + = render 'crops/tiny', crop: crop -.pagination= render 'layouts/pagination', collection: @posts += will_paginate @posts %p - if @author diff --git a/app/views/posts/show.html.haml b/app/views/posts/show.html.haml index f88612496..efa29e670 100644 --- a/app/views/posts/show.html.haml +++ b/app/views/posts/show.html.haml @@ -51,16 +51,17 @@ = icon 'fas', 'comment' Comment - %secion.comments= render "comments", post: @post + %section.comments + = render "comments", post: @post .col-md-4.col-12 = render @post.author - .row - - unless @post.crops.empty? - .col-12 - %h3.h3 Crops mentioned in this post - - @post.crops.each do |c| - .col-6= render 'crops/thumbnail', crop: c + - unless @post.crops.approved.empty? + %section.crops + %h2 Crops mentioned in this post + .index-cards + - @post.crops.approved.each do |c| + = render 'crops/thumbnail', crop: c diff --git a/app/views/roles/_form.html.haml b/app/views/roles/_form.html.haml deleted file mode 100644 index 67393b833..000000000 --- a/app/views/roles/_form.html.haml +++ /dev/null @@ -1,19 +0,0 @@ -= form_for @role do |f| - .row - - if @role.errors.any? - #error_explanation - %h2 - = pluralize(@role.errors.size, "error") - prohibited this role from being saved: - %ul - - @role.errors.full_messages.each do |msg| - %li= msg - - .field - .col-md2= f.label :name - .col-md10= f.text_field :name - .field - .col-md2= f.label :description - .col-md10= f.text_area :description - .actions - = f.submit 'Save', class: 'btn btn-default' diff --git a/app/views/roles/edit.html.haml b/app/views/roles/edit.html.haml deleted file mode 100644 index f0864018c..000000000 --- a/app/views/roles/edit.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -- content_for :title, "Editing role" - -= render 'form' - -= link_to 'Back', roles_path diff --git a/app/views/roles/index.html.haml b/app/views/roles/index.html.haml deleted file mode 100644 index 9a19a7959..000000000 --- a/app/views/roles/index.html.haml +++ /dev/null @@ -1,21 +0,0 @@ -- content_for :title, "Listing roles" - -- if can? :create, Role - %p= link_to 'New Role', new_role_path, class: 'btn btn-primary' - -%table.table.table-striped - %tr - %th Name - %th Description - %th - %th - - - @roles.each do |role| - %tr - %td= role.name - %td= role.description - - if can? :edit, role - %td= link_to 'Edit', edit_role_path(role), class: 'btn btn-default btn-xs' - - if can? :destroy, role - %td= link_to 'Delete', role, method: :delete, - data: { confirm: 'Are you sure?' }, class: 'btn btn-default btn-xs' diff --git a/app/views/roles/new.html.haml b/app/views/roles/new.html.haml deleted file mode 100644 index 30aae3fe1..000000000 --- a/app/views/roles/new.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -- content_for :title, "New role" - -= render 'form' - -= link_to 'Back', roles_path diff --git a/app/views/roles/show.html.haml b/app/views/roles/show.html.haml deleted file mode 100644 index 54d229012..000000000 --- a/app/views/roles/show.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -%p#notice= notice - -%p - %b Name: - = @role.name -%p - %b Description: - = @role.description - -= link_to 'Edit', edit_role_path(@role), class: 'btn btn-default btn-xs' -\| -= link_to 'Back', roles_path diff --git a/app/views/scientific_names/_form.html.haml b/app/views/scientific_names/_form.html.haml index 95178f068..95f768aa7 100644 --- a/app/views/scientific_names/_form.html.haml +++ b/app/views/scientific_names/_form.html.haml @@ -17,7 +17,7 @@ .form-group = f.label :crop_id, class: 'control-label col-md-2' .col-md-8 - = collection_select(:scientific_name, :crop_id, Crop.all, :id, + = collection_select(:scientific_name, :crop_id, Crop.all.order(:name), :id, :name, { selected: @scientific_name.crop_id || @crop.id }, class: 'form-control') .form-group diff --git a/app/views/seeds/_actions.html.haml b/app/views/seeds/_actions.html.haml index 716724399..5cc91951a 100644 --- a/app/views/seeds/_actions.html.haml +++ b/app/views/seeds/_actions.html.haml @@ -1,12 +1,12 @@ -- if can?(:create, Planting) && can?(:update, seed) && seed.active? - = link_to new_planting_path(seed_id: seed), class: 'btn btn-info' do - %span.glyphicon.glyphicon-grain{ title: "Plant seeds" } - Plant seeds - if can?(:edit, seed) - .dropdown.float-right.seed-actions - %a#seed-actions-button.btn.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", type: "button", href: '#'} + .dropdown.seed-actions + %a#seed-actions-button.btn.btn-info.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", type: "button", href: '#'} Actions .dropdown-menu.dropdown-menu-xs{"aria-labelledby" => "seed-actions-button"} + - if can?(:create, Planting) && can?(:update, seed) && seed.active? + = link_to new_planting_path(seed_id: seed), class: 'dropdown-item success-color' do + = seed_icon + Plant seeds = seed_edit_button(seed, classes: 'dropdown-item') = add_photo_button(seed, classes: 'dropdown-item') - if seed.active? diff --git a/app/views/seeds/_card.html.haml b/app/views/seeds/_card.html.haml index cd7db9099..45164e45b 100644 --- a/app/views/seeds/_card.html.haml +++ b/app/views/seeds/_card.html.haml @@ -1,13 +1,16 @@ -.card.seed-card +.card.seed-card{class: seed.active? ? '' : 'card-finished'} = link_to seed do - = image_tag(seed_image_path(seed, full_size: true), alt: seed, class: 'img-card') - .card-body - .card-title= link_to seed.crop, seed - .card-footer + = image_tag(seed_image_path(seed), alt: seed, class: 'img-card') + .text = render 'members/tiny', member: seed.owner + .card-body + .card-title + = crop_icon(seed.crop) + = link_to seed.crop, seed - if seed.tradable? - %span.badge.badge-pill.badge-info + .text-muted = icon 'fas', 'map' Will trade #{seed.tradable_to} - %span.badge.badge-pill.badge-secondary - = truncate(seed.owner.location, length: 30, separator: ' ', omission: '... ') \ No newline at end of file + .badge.badge-pill.badge-location + = icon 'fas', 'map-marker' + = truncate(seed.owner.location, length: 20, separator: ' ', omission: '... ') \ No newline at end of file diff --git a/app/views/seeds/_descendants.html.haml b/app/views/seeds/_descendants.html.haml index 1736d0bca..3a2a445aa 100644 --- a/app/views/seeds/_descendants.html.haml +++ b/app/views/seeds/_descendants.html.haml @@ -1,13 +1,5 @@ -%h2 Plants grown from these seeds -- if @seed.child_plantings - .row +- if @seed.child_plantings.any? + %h2 Plants grown from these seeds + .index-cards - seed.child_plantings.each do |planting| - .col-md-3 - = render 'plantings/thumbnail', planting: planting -- else - %p No plants grown yet. - -- if can?(:create, Planting) && can?(:edit, seed) - = link_to new_seed_planting_path(seed), class: 'btn btn-primary' do - %span.glyphicon.glyphicon-grain{ title: "Plant seeds" } - Plant seeds + = render 'plantings/thumbnail', planting: planting diff --git a/app/views/seeds/_facts.html.haml b/app/views/seeds/_facts.html.haml new file mode 100644 index 000000000..79ffb6860 --- /dev/null +++ b/app/views/seeds/_facts.html.haml @@ -0,0 +1,49 @@ +.index-cards.facts.seedfacts + .card{class: seed.quantity.present? ? '' : 'text-muted'} + %h3 + Quantity + = editable :text_field, seed, :quantity, display_field: '.seedfacts--quantity' + %strong.seedfacts--quantity= seed.quantity.blank? ? "not specified" : seed.quantity + %span + seeds + + .card.seedfacts--card{class: seed.plant_before.present? ? '' : 'text-muted'} + %h3 + Plant before + = editable :date, seed, :plant_before, display_field: '.seedfacts--plantbefore' + %strong.seedfacts--plantbefore + - if seed.plant_before.present? + = I18n.l(seed.plant_before) + - else + Unknown + + + .card{class: seed.days_until_maturity_max.present? ? '' : 'text-muted'} + %h3 Days until maturity + %strong.seedfacts--maturity + = render partial: 'days_until_maturity', locals: { seed: seed } + %span days + + .card + %h3 + Will trade + = editable :select, seed, :tradable_to, collection: Seed::TRADABLE_TO_VALUES, display_field: '.seedfacts--tradableto' + %strong.seedfacts--tradableto= seed.tradable_to + %span + - if seed.owner.location.blank? + (from unspecified location) + - if current_member == seed.owner + = link_to "Set Location", edit_registration_path(current_member), class: 'btn btn-default btn-xs' + - else + (from #{link_to seed.owner.location, place_path(seed.owner.location, anchor: "seeds")}) + + - if seed.finished_at.present? || seed.finished? + .card + %h3 Finished + %strong + - if seed.finished_at.present? + = I18n.t('date.abbr_month_names')[seed.finished_at.month] + -elsif seed.finished? + = finished_icon + - if seed.finished_at.present? + %span= seed.finished_at.year \ No newline at end of file diff --git a/app/views/seeds/_form.html.haml b/app/views/seeds/_form.html.haml index 5d0facb58..62e8033ca 100644 --- a/app/views/seeds/_form.html.haml +++ b/app/views/seeds/_form.html.haml @@ -28,17 +28,21 @@ Can't find what you're looking for? = link_to "Request new crops.", new_crop_path .row - .col-12= f.number_field :quantity, label: 'Quantity' - .col-12 + .col-12.col-md-4 + = f.text_field :saved_at, + value: @seed.saved_at ? @seed.saved_at.to_s(:ymd) : '', + class: 'add-datepicker', label: 'When were the seeds harvested/saved?' + .col-12.col-md-4= f.number_field :quantity, label: 'Quantity' + .col-12.col-md-4 = f.text_field :plant_before, class: 'add-datepicker', value: @seed.plant_before ? @seed.plant_before.to_s(:ymd) : '' .row - .col-12 + .col-12.col-md-4 = f.check_box :finished, label: 'Mark as finished' - .col-12 + .col-12.col-md-4 = f.text_field :finished_at, class: 'add-datepicker', value: @seed.finished_at ? @seed.finished_at.to_s(:ymd) : '' - .col-12 + .col-12.col-md-4 %span.help-inline= t('.finish_helper') .row diff --git a/app/views/seeds/_owner.html.haml b/app/views/seeds/_owner.html.haml new file mode 100644 index 000000000..197082095 --- /dev/null +++ b/app/views/seeds/_owner.html.haml @@ -0,0 +1,17 @@ +.card + .card-body + .row + .col-md-8 + %h4 + Saved by + = link_to @seed.owner, @seed.owner + = link_to "view all #{@seed.owner}'s seeds", member_seeds_path(@seed.owner) + + .col-md-4= render "members/avatar", member: @seed.owner + .row + .col-12 + - if @seed.owner.location + %p + %small + View other seeds, members and more near + = link_to @seed.owner.location, place_path(@seed.owner.location, anchor: "seeds") \ No newline at end of file diff --git a/app/views/seeds/_seed.haml b/app/views/seeds/_seed.haml index af5f6203f..b6cd078e2 100644 --- a/app/views/seeds/_seed.haml +++ b/app/views/seeds/_seed.haml @@ -3,4 +3,4 @@ = render 'seeds/card', seed: seed - else - cache seed do - = render 'seeds/thumbnail', seed: seed \ No newline at end of file + = render 'seeds/thumbnail', seed: seed diff --git a/app/views/seeds/_thumbnail.html.haml b/app/views/seeds/_thumbnail.html.haml index a35937357..0aefcaac8 100644 --- a/app/views/seeds/_thumbnail.html.haml +++ b/app/views/seeds/_thumbnail.html.haml @@ -5,5 +5,6 @@ seed_path(seed) .seedinfo .seed-name - = link_to display_seed_quantity(seed), seed_path(seed) - = I18n.l(seed.created_at.to_date) + = link_to seed.crop, seed_path(seed) +- if seed.saved_at.present? + %span= I18n.l(seed.saved_at.to_date) diff --git a/app/views/seeds/index.html.haml b/app/views/seeds/index.html.haml index 2b366b8c1..6c74b13d3 100644 --- a/app/views/seeds/index.html.haml +++ b/app/views/seeds/index.html.haml @@ -1,17 +1,3 @@ -- content_for :title, title('seeds', @owner, @crop, @planting) -%h1 - = seed_icon - = title('seeds', @owner, @crop, @planting) - -- if @owner - = link_to "View #{@owner}'s profile >>", member_path(@owner) - -%p - #{ENV['GROWSTUFF_SITE_NAME']} helps you track your seed - stash or trade seeds with other members. - -= render 'layouts/nav', model: Seed - - content_for :breadcrumbs do - if @owner %li.breadcrumb-item= link_to 'Seeds', seeds_path @@ -19,32 +5,39 @@ - else %li.breadcrumb-item.active= link_to 'Seeds', seeds_path +- content_for :title, title('seeds', @owner, @crop, @planting) +%h1 + = seed_icon + = title('seeds', @owner, @crop, @planting) + +.row + .col + %ul.nav + %li.list-group-item + = link_to show_inactive_tickbox_path('seeds', owner: @owner, show_all: @show_all) do + = check_box_tag 'active', 'all', @show_all + include finished + - if @owner + %li.list-group-item.btn + = link_to t('.view_owners_profile', owner: @owner), member_path(@owner) + .col= render 'layouts/nav', model: Seed - unless @seeds.empty? - .pagination - = page_entries_info @seeds - = will_paginate @seeds - .index-cards= render @seeds, full: true + = page_entries_info @seeds + = will_paginate @seeds - .pagination - = page_entries_info @seeds - = will_paginate @seeds + .index-cards + - @seeds.each do |seed| + = render 'seeds/card', seed: seed - %ul.nav.justify-content-center - %li.nav-item - The data on this page is available in the following formats: - - if @owner - %li.nav-item - = link_to "CSV", member_seeds_path(@owner, format: 'csv'), class: 'nav-link' - %li.nav-item - = link_to "JSON", member_seeds_path(@owner, format: 'json'), class: 'nav-link' - %li.nav-item - = link_to "RSS", member_seeds_path(@owner, format: 'rss'), class: 'nav-link' - - else - %li.nav-item - = link_to "CSV", seeds_path(format: 'csv'), class: 'nav-link' - %li.nav-item - = link_to "JSON", seeds_path(format: 'json'), class: 'nav-link' - %li.nav-item - = link_to "RSS", seeds_path(format: 'rss'), class: 'nav-link' + = will_paginate @seeds + +%section.open-data + %h5= t('label.data') + %ul.nav#open-data + - ['csv', 'json', 'rss'].each do |format| + %li.list-group-item + = link_to (@owner ? member_seeds_path(@owner, format: format) : seeds_path(format: format)) do + = icon 'fas', format.to_s + = format.upcase diff --git a/app/views/seeds/show.html.haml b/app/views/seeds/show.html.haml index 07aec7b53..14917dc9e 100644 --- a/app/views/seeds/show.html.haml +++ b/app/views/seeds/show.html.haml @@ -12,80 +12,58 @@ - content_for :breadcrumbs do %li.breadcrumb-item= link_to 'Seeds', seeds_path + %li.breadcrumb-item= link_to @seed.owner, member_seeds_path(@seed.owner) %li.breadcrumb-item.active= link_to @seed, @seed -.row - .col-md-6 - %h1 - #{@seed.owner}'s #{@seed.crop} seeds +.seed + .row + .col-md-9.col-12 + .jumbotron + .d-flex.justify-content-between + %h1.display-3 + = crop_icon(@seed.crop) + = @seed.crop.name.titleize + seeds + %span.text-muted= I18n.l @seed.created_at.to_date + - if @seed.organic != 'unknown' + .badge.badge-success.seedtitle--organic= @seed.organic + - if @seed.gmo != 'unknown' + .badge.badge-success.seedtitle--gmo= @seed.gmo + - if @seed.heirloom != 'unknown' + .badge.badge-success.seedtitle--heirloom= @seed.heirloom + - if @seed.parent_planting + %p + Saved from planting: + = planting_icon + = link_to @seed.parent_planting, planting_path(@seed.parent_planting) = render 'seeds/actions', seed: @seed - %dl.dl-horizontal - %dt Owner - %dd - = link_to @seed.owner, @seed.owner - — - = link_to "view all #{@seed.owner}'s seeds", - member_seeds_path(@seed.owner) - %dt Quantity: - %dd= @seed.quantity.blank? ? "not specified" : @seed.quantity - %dt Plant before: - %dd= @seed.plant_before.to_s - -if @seed.finished_at - %dt Finished at: - %dd= @seed.finished_at.to_s - %dt Days until maturity: - %dd= render partial: 'days_until_maturity', locals: { seed: @seed } - %dt Organic? - %dd= @seed.organic - %dt GMO? - %dd= @seed.gmo - %dt Heirloom? - %dd= @seed.heirloom - %dt Will trade: - %dd - = @seed.tradable_to - - if @seed.owner.location.blank? - (from unspecified location) - - if current_member == @seed.owner - = link_to "Set Location", edit_registration_path(current_member), class: 'btn btn-default btn-xs' - - else - (from - = succeed ")" do - = link_to @seed.owner.location, place_path(@seed.owner.location, anchor: "seeds") - %dt When? - %dd - = @seed.created_at - - if @seed.parent_planting - %dt Saved from planting: - %dd - = link_to @seed.parent_planting, planting_path(@seed.parent_planting) - %dt Description: - %dd - :growstuff_markdown - #{ @seed.description != "" ? strip_tags(@seed.description) : "No description given." } + %section= render 'facts', seed: @seed - - if current_member - - if @seed.tradable? && current_member != @seed.owner - %p= link_to "Request seeds", - new_message_path(recipient_id: @seed.owner.id, - subject: "Interested in your #{@seed.crop} seeds"), - class: 'btn btn-primary' - - else - = render 'shared/signin_signup', to: 'request seeds' + - unless @seed.description.blank? + = cute_icon + .card.seed--description + .card-header + %h2 Notes + .card-body + :growstuff_markdown + #{strip_tags(@seed.description)} - = render 'seeds/descendants', seed: @seed - = render 'photos/item_photos', item: @seed, type: 'seed', photos: @photos + - if current_member + - if @seed.tradable? && current_member != @seed.owner + %p= link_to "Request seeds", + new_message_path(recipient_id: @seed.owner.id, + subject: "Interested in your #{@seed.crop} seeds"), + class: 'btn btn-primary' + - else + = render 'shared/signin_signup', to: 'request seeds' - .col-md-6 - = render @seed.crop - - if @seed.owner.location - %p - %small - View other seeds, members to trade with and more near - = link_to @seed.owner.location, place_path(@seed.owner.location, anchor: "seeds") - %p - %small - Or - = link_to "purchase seeds via Ebay", - crop_ebay_seeds_url(@seed.crop), target: "_blank", rel: "noopener noreferrer" + %section.plantings + = render 'seeds/descendants', seed: @seed + %section.seed-photos + - @photos.each do |photo| + = render 'photos/hero', photo: photo + + .col-md-3.col-12 + = render 'seeds/owner' + = render @seed.crop diff --git a/app/views/shared/_global_actions.html.haml b/app/views/shared/_global_actions.html.haml deleted file mode 100644 index a40849af9..000000000 --- a/app/views/shared/_global_actions.html.haml +++ /dev/null @@ -1,25 +0,0 @@ -- if signed_in? - .btn-group - = link_to member_gardens_path(member_slug: current_member.slug), - class: 'btn btn-default', 'data-toggle': "tooltip", 'data-placement': "bottom", title: t('buttons.my_gardens') do - = garden_icon - - = link_to timeline_index_path, - class: 'btn btn-default', 'data-toggle': "tooltip", 'data-placement': "bottom", title: t('buttons.timeline') do - = timeline_icon - - = link_to new_planting_path, - class: 'btn btn-default', 'data-toggle': "tooltip", 'data-placement': "bottom", title: t('buttons.new_planting') do - = planting_icon - - = link_to new_harvest_path, - class: 'btn btn-default', 'data-toggle': "tooltip", 'data-placement': "bottom", title: t('buttons.new_harvest') do - = harvest_icon - - = link_to new_seed_path, - class: 'btn btn-default', 'data-toggle': "tooltip", 'data-placement': "bottom", title: t('buttons.new_seeds') do - = seed_icon - - = link_to new_post_path, - class: 'btn btn-default', 'data-toggle': "tooltip", 'data-placement': "bottom", title: t('buttons.new_post') do - = post_icon diff --git a/app/views/shared/_signin_signup.html.haml b/app/views/shared/_signin_signup.html.haml index 5fae2b0bd..51c7a5ac2 100644 --- a/app/views/shared/_signin_signup.html.haml +++ b/app/views/shared/_signin_signup.html.haml @@ -1,7 +1,6 @@ -%p - = link_to 'Sign in', new_member_session_path, class: 'btn btn-success' - or - = link_to 'sign up', new_member_registration_path, class: 'btn btn-info' - to - = succeed "." do - = to += link_to 'Sign in', new_member_session_path, class: 'btn btn-success' +or += link_to 'sign up', new_member_registration_path, class: 'btn btn-info' +to += succeed "." do + = to diff --git a/app/views/shared/editable/_date.html.haml b/app/views/shared/editable/_date.html.haml new file mode 100644 index 000000000..3e9fccfbb --- /dev/null +++ b/app/views/shared/editable/_date.html.haml @@ -0,0 +1,11 @@ +- if can? :edit, model + %small.edit-link + %a.editable-date#description{"data-field" => "#date--#{model.id}-#{field.to_s}", + "data-display": display_field, href: "#"} + = edit_icon + .hide{id: "date--#{model.id}-#{field.to_s}"} + = bootstrap_form_for(model) do |f| + = f.text_field field, + value: model.send(field) ? model.send(field).to_s(:ymd) : '', + class: 'add-datepicker', label: 'When?' + = f.submit :save diff --git a/app/views/shared/editable/_form.html.haml b/app/views/shared/editable/_form.html.haml new file mode 100644 index 000000000..1c51efdae --- /dev/null +++ b/app/views/shared/editable/_form.html.haml @@ -0,0 +1,18 @@ +- if can? :edit, model + %small.edit-link + %a.editable{"data-form" => "#form--#{model.id}-#{field.to_s}", "data-display": display_field, href: "#", name: "#form--#{model.id}-#{field.to_s}"} + = edit_icon + + .hide{id: "form--#{model.id}-#{field.to_s}"} + = bootstrap_form_for(model) do |f| + - if field_type == :text_area + = f.text_area field + - elsif field_type == :text_field + = f.text_field field + - elsif field_type == :select + = f.select field, collection + - elsif field_type == :date + = f.text_field field, + value: model.send(field) ? model.send(field).to_s(:ymd) : '', + class: 'add-datepicker', label: 'When?' + = f.submit :save diff --git a/app/views/timeline/_photos.html.haml b/app/views/timeline/_photos.html.haml index 12cddb1a2..732842c39 100644 --- a/app/views/timeline/_photos.html.haml +++ b/app/views/timeline/_photos.html.haml @@ -1,8 +1,11 @@ .media - = link_to(image_tag(photo.fullsize_url, width: 150, class: 'rounded'), photo) + = link_to(image_tag(photo.thumbnail_url, width: 150, class: 'rounded'), photo) .media-body %p %ul.associations + - photo.crops.each do |crop| + %li= render 'crops/tiny', crop: crop + - photo.plantings.each do |planting| %li = planting_icon diff --git a/app/views/timeline/index.html.haml b/app/views/timeline/index.html.haml index 1f374bc20..f94cafd1b 100644 --- a/app/views/timeline/index.html.haml +++ b/app/views/timeline/index.html.haml @@ -23,10 +23,7 @@ .pagination= will_paginate @seeds .col-md-3 .card - .card-header + .card-body %h3 Following - - @members.each do |member| - = link_to member do - %p - = render 'members/tiny', member: member - = member + - @members.each do |member| + = render 'members/tiny', member: member diff --git a/config/locales/en.yml b/config/locales/en.yml index 11dd0a781..5285d5569 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -132,8 +132,8 @@ en: blurb: already_html: Or %{sign_in} if you already have an account intro: > - %{site_name} is a community of food gardeners. We're building an open source platform to help you learn about growing food, - track what you plant and harvest, and swap seeds and produce with other gardeners near you. + %{site_name} is a community of food gardeners. %{site_name}'s open source and open data platform + can predict when your plantings will be ready to harvest. The more you tell it, the better the predictions. perks: Join now for your free garden journal, harvest predictions, forums, and more. sign_in_linktext: sign in sign_up: Sign up @@ -206,8 +206,11 @@ en: number_plantings_linktext: "%{count} times" label: days_until_harvest: "%{number} days" + weeks_until_harvest: "%{number} weeks until harvest" days_until_finished: "%{number} days" + weeks_until_finished: "%{number} weeks" harvesting_now: harvesting now + data: 'The data on this page is available in the following formats:' layouts: header: account: Account @@ -225,6 +228,7 @@ en: gardens: Gardens harvest: Harvest harvests: Harvests + record: Record inbox: Inbox inbox_unread: Inbox (%{unread_count}) plantings: Plantings @@ -289,7 +293,6 @@ en: default: Everyone's plantings owner_plantings: "%{owner} plantings" view_owners_profile: View %{owner}'s profile >> - the_data_on_this_page_is_available_in_the_following_formats: 'The data on this page is available in the following formats:' string: "%{crop} planting in %{garden} by %{owner}" progress: progress_0_not_planted_yet: 'Progress: 0% - not planted yet' diff --git a/config/puma.rb b/config/puma.rb index a5eccf816..bcf8670ad 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -1,19 +1,24 @@ +# frozen_string_literal: true + +# Overview of config: +# https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server + # Puma can serve each request in a thread from an internal thread pool. # The `threads` method setting takes two numbers: a minimum and maximum. # Any libraries that use thread pools should be configured to match # the maximum value specified for Puma. Default is set to 5 threads for minimum # and maximum; this matches the default thread size of Active Record. # -threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } +threads_count = ENV.fetch('RAILS_MAX_THREADS') { 5 } threads threads_count, threads_count # Specifies the `port` that Puma will listen on to receive requests; default is 3000. # -port ENV.fetch("PORT") { 3000 } +port ENV.fetch('PORT') { 3000 } # Specifies the `environment` that Puma will run in. # -environment ENV.fetch("RAILS_ENV") { "development" } +environment ENV.fetch('RAILS_ENV') { 'development' } # Specifies the number of `workers` to boot in clustered mode. # Workers are forked webserver processes. If using threads and workers together @@ -21,14 +26,20 @@ environment ENV.fetch("RAILS_ENV") { "development" } # Workers do not work on JRuby or Windows (both of which do not support # processes). # -# workers ENV.fetch("WEB_CONCURRENCY") { 2 } +workers ENV.fetch('WEB_CONCURRENCY') { 2 } # Use the `preload_app!` method when specifying a `workers` number. # This directive tells Puma to first boot the application and load code # before forking the application. This takes advantage of Copy On Write # process behavior so workers use less memory. # -# preload_app! +preload_app! + +on_worker_boot do + # Worker specific setup for Rails 4.1+ + # See: https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server#on-worker-boot + ActiveRecord::Base.establish_connection +end # Allow puma to be restarted by `rails restart` command. plugin :tmp_restart diff --git a/config/routes.rb b/config/routes.rb index e1424d010..565e4a0ea 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -69,6 +69,7 @@ Rails.application.routes.draw do get 'sunniness' => 'charts/crops#sunniness', constraints: { format: 'json' } get 'planted_from' => 'charts/crops#planted_from', constraints: { format: 'json' } get 'harvested_for' => 'charts/crops#harvested_for', constraints: { format: 'json' } + post :openfarm collection do get 'requested' @@ -79,7 +80,6 @@ Rails.application.routes.draw do end resources :comments - resources :roles resources :forums resources :follows, only: %i(create destroy) @@ -98,7 +98,11 @@ Rails.application.routes.draw do end resources :messages - resources :conversations + resources :conversations do + collection do + delete 'destroy_multiple' + end + end resources :places, only: %i(index show), param: :place do get 'search', on: :collection @@ -108,12 +112,16 @@ Rails.application.routes.draw do get 'members/auth/:provider/callback' => 'authentications#create' scope :admin do - resources :members, param: :slug, controller: 'admin/members', as: 'admin_members' get '/' => 'admin#index', as: 'admin' get '/newsletter' => 'admin#newsletter', as: 'admin_newsletter' comfy_route :cms_admin, path: '/cms' end + namespace :admin do + resources :members, param: :slug + resources :roles + end + namespace :api do namespace :v1 do jsonapi_resources :photos diff --git a/db/migrate/20190902004225_add_openfarm_data_to_crops.rb b/db/migrate/20190902004225_add_openfarm_data_to_crops.rb new file mode 100644 index 000000000..f8cc4d43d --- /dev/null +++ b/db/migrate/20190902004225_add_openfarm_data_to_crops.rb @@ -0,0 +1,5 @@ +class AddOpenfarmDataToCrops < ActiveRecord::Migration[5.2] + def change + add_column :crops, :openfarm_data, :jsonb + end +end diff --git a/db/migrate/20190910022329_add_photo_source.rb b/db/migrate/20190910022329_add_photo_source.rb new file mode 100644 index 000000000..44e9a9d7d --- /dev/null +++ b/db/migrate/20190910022329_add_photo_source.rb @@ -0,0 +1,7 @@ +class AddPhotoSource < ActiveRecord::Migration[5.2] + def change + add_column :photos, :source, :string + rename_column :photos, :flickr_photo_id, :source_id + Photo.update_all(source: 'flickr') # rubocop:disable Rails/SkipsModelValidations + end +end diff --git a/db/migrate/20190915065209_create_crop_companions.rb b/db/migrate/20190915065209_create_crop_companions.rb new file mode 100644 index 000000000..1b9d1b974 --- /dev/null +++ b/db/migrate/20190915065209_create_crop_companions.rb @@ -0,0 +1,9 @@ +class CreateCropCompanions < ActiveRecord::Migration[5.2] + def change + create_table :crop_companions do |t| + t.integer "crop_a_id", null: false + t.integer "crop_b_id", null: false + t.timestamps null: false + end + end +end diff --git a/db/migrate/20190918033319_unique_urls.rb b/db/migrate/20190918033319_unique_urls.rb new file mode 100644 index 000000000..99b9caf42 --- /dev/null +++ b/db/migrate/20190918033319_unique_urls.rb @@ -0,0 +1,6 @@ +class UniqueUrls < ActiveRecord::Migration[5.2] + def change + add_index :photos, :fullsize_url, unique: true + add_index :photos, :thumbnail_url, unique: true + end +end diff --git a/db/migrate/20190921211652_crop_posts.rb b/db/migrate/20190921211652_crop_posts.rb new file mode 100644 index 000000000..c7f2950c5 --- /dev/null +++ b/db/migrate/20190921211652_crop_posts.rb @@ -0,0 +1,5 @@ +class CropPosts < ActiveRecord::Migration[5.2] + def change + rename_table :crops_posts, :crop_posts + end +end diff --git a/db/migrate/20191029024101_add_saved_at_to_seeds.rb b/db/migrate/20191029024101_add_saved_at_to_seeds.rb new file mode 100644 index 000000000..745bdf5e4 --- /dev/null +++ b/db/migrate/20191029024101_add_saved_at_to_seeds.rb @@ -0,0 +1,5 @@ +class AddSavedAtToSeeds < ActiveRecord::Migration[5.2] + def change + add_column :seeds, :saved_at, :date + end +end diff --git a/db/schema.rb b/db/schema.rb index 1ce9e5deb..041da08a4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_07_21_042146) do +ActiveRecord::Schema.define(version: 2019_10_29_024101) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -153,6 +153,20 @@ ActiveRecord::Schema.define(version: 2019_07_21_042146) do t.datetime "updated_at" end + create_table "crop_companions", force: :cascade do |t| + t.integer "crop_a_id", null: false + t.integer "crop_b_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "crop_posts", id: false, force: :cascade do |t| + t.integer "crop_id" + t.integer "post_id" + t.index ["crop_id", "post_id"], name: "index_crop_posts_on_crop_id_and_post_id" + t.index ["crop_id"], name: "index_crop_posts_on_crop_id" + end + create_table "crops", id: :serial, force: :cascade do |t| t.string "name", null: false t.string "en_wikipedia_url" @@ -171,18 +185,12 @@ ActiveRecord::Schema.define(version: 2019_07_21_042146) do t.integer "median_lifespan" t.integer "median_days_to_first_harvest" t.integer "median_days_to_last_harvest" + t.jsonb "openfarm_data" t.index ["name"], name: "index_crops_on_name" t.index ["requester_id"], name: "index_crops_on_requester_id" t.index ["slug"], name: "index_crops_on_slug", unique: true end - create_table "crops_posts", id: false, force: :cascade do |t| - t.integer "crop_id" - t.integer "post_id" - t.index ["crop_id", "post_id"], name: "index_crops_posts_on_crop_id_and_post_id" - t.index ["crop_id"], name: "index_crops_posts_on_crop_id" - end - create_table "follows", id: :serial, force: :cascade do |t| t.integer "follower_id" t.integer "followed_id" @@ -416,9 +424,12 @@ ActiveRecord::Schema.define(version: 2019_07_21_042146) do t.string "license_name", null: false t.string "license_url" t.string "link_url", null: false - t.string "flickr_photo_id" + t.string "source_id" t.datetime "date_taken" t.integer "likes_count", default: 0 + t.string "source" + t.index ["fullsize_url"], name: "index_photos_on_fullsize_url", unique: true + t.index ["thumbnail_url"], name: "index_photos_on_thumbnail_url", unique: true end create_table "photos_plantings", id: false, force: :cascade do |t| @@ -509,6 +520,7 @@ ActiveRecord::Schema.define(version: 2019_07_21_042146) do t.boolean "finished", default: false t.date "finished_at" t.integer "parent_planting_id" + t.date "saved_at" t.index ["slug"], name: "index_seeds_on_slug", unique: true end diff --git a/db/seeds.rb b/db/seeds.rb index 378b98875..e3bc51088 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -115,6 +115,8 @@ def load_admin_users end def create_cropbot + return if Member.find_by(login_name: 'cropbot') + @cropbot_user = Member.new( login_name: "cropbot", email: Rails.application.config.bot_email, diff --git a/lib/tasks/growstuff.rake b/lib/tasks/growstuff.rake index d36b7bd95..6c185c7bc 100644 --- a/lib/tasks/growstuff.rake +++ b/lib/tasks/growstuff.rake @@ -44,209 +44,11 @@ namespace :growstuff do task send_planting_reminder: :environment do # Heroku scheduler only lets us run things daily, so this checks - # whether it's the right day to actually do the deed. - # Note that Heroku scheduler runs on UTC. - # We'd like to send on Wednesday mornings, US time, which will be - # very early Wednesday morning UTC. - send_on_day = 3 # wednesday - every_n_weeks = 2 # send fortnightly - - if (Time.zone.today.cwday == send_on_day) && (Time.zone.today.cweek % every_n_weeks == 0) - Member.confirmed.find_each do |m| - Notifier.planting_reminder(m).deliver_now! + # Send on the first of the month + if Time.zone.today.day == 1 + Member.confirmed.wants_reminders.each do |m| + Notifier.planting_reminder(m).deliver_now! unless m.plantings.active.empty? end end end - - desc "Depopulate Null Island" - # this fixes up anyone who has erroneously wound up with a 0,0 lat/long - task depopulate_null_island: :environment do - Member.find_each do |m| - if m.location && (m.latitude.nil? && m.longitude.nil?) - m.geocode - m.save - end - end - end - - desc "One-off tasks needed at various times and kept for posterity" - namespace :oneoff do - desc "May 2013: replace any empty notification subjects with (no subject)" - task empty_subjects: :environment do - # this is inefficient as it checks every Notification, but the - # site is small and there aren't many of them, so it shouldn't matter - # for this one-off script. - Notification.all.each do |n| - n.replace_blank_subject - n.save - end - end - - desc "May 2013: replace any empty garden names with Garden" - task empty_garden_names: :environment do - # this is inefficient as it checks every Garden, but the - # site is small and there aren't many of them, so it shouldn't matter - # for this one-off script. - Garden.all.each do |g| - if g.name.nil? || g.name =~ /^\s*$/ - g.name = "Garden" - g.save - end - end - end - - desc "July 2013: replace nil seed.tradable_to with nowhere" - task tradable_to_nowhere: :environment do - Seed.all.each do |s| - unless s.tradable_to - s.tradable_to = 'nowhere' - s.save - end - end - end - - desc "August 2013: set up plantings_count cache on crop" - task reset_crop_plantings_count: :environment do - Crop.find_each do |c| - Crop.reset_counters c.id, :plantings - end - end - - desc "August 2013: set default creator on existing crops" - task set_default_crop_creator: :environment do - cropbot = Member.find_by(login_name: "cropbot") - raise "cropbot not found: create cropbot member on site or run rake db:seed" unless cropbot - - Crop.find_each do |crop| - unless crop.creator - crop.creator = cropbot - crop.save - end - end - ScientificName.find_each do |sn| - unless sn.creator - sn.creator = cropbot - sn.save - end - end - end - - desc "August 2013: set planting owner" - task set_planting_owner: :environment do - Planting.find_each do |p| - p.owner = p.garden.owner - p.save - end - end - - desc "August 2013: initialize member planting counter" - task initialize_member_planting_count: :environment do - Member.find_each do |m| - Member.reset_counters m.id, :plantings - end - end - - desc "October 2013: set garden locations to member locations" - task initialize_garden_locations: :environment do - Member.located.find_each do |m| - m.gardens.each do |g| - next if g.location.present? - - g.location = m.location - g.latitude = m.latitude - g.longitude = m.longitude - g.save - end - end - end - - desc "October 2013: import initial plant parts" - task import_plant_parts: :environment do - plant_parts = [ - 'fruit', - 'flower', - 'seed', - 'pod', - 'leaf', - 'stem', - 'bark', - 'bulb', - 'root', - 'tuber', - 'whole plant', - 'other' - ] - plant_parts.each do |pp| - PlantPart.find_or_create_by!(name: pp) - end - end - - desc "July 2014: set planting_count to 0 by default, not nil" - task zero_plantings_count: :environment do - Crop.find_each do |c| - if c.plantings_count.nil? - c.plantings_count = 0 - c.save - end - end - end - - desc "August 2014: fix ping to pint in database" - task ping_to_pint: :environment do - Harvest.find_each do |h| - if h.unit == "ping" - h.unit = "pint" - h.save - end - end - end - - desc "October 2014: remove unused photos" - task remove_unused_photos: :environment do - Photo.find_each(&:destroy_if_unused) - end - - desc "October 2014: generate crops_posts records for existing posts" - task generate_crops_posts_records: :environment do - Post.find_each(&:save) - end - - desc "October 2014: add alternate names for crops" - task add_alternate_names: :environment do - require 'csv' - file = "db/seeds/alternate_names_201410.csv" - puts "Loading alternate names from #{file}..." - cropbot = Member.find_by(login_name: "cropbot") - CSV.foreach(file) do |row| - _crop_id, crop_name, alternate_names = row - next if alternate_names.blank? - - crop = Crop.find_by(name: crop_name) - if crop - alternate_names.split(/,\s*/).each do |an| - AlternateName.where( - name: an, - crop_id: crop.id - ).first_or_create do |x| - x.creator = cropbot - end - end - end - end - end - - desc "January 2015: fill in si_weight column" - task populate_si_weight: :environment do - Harvest.find_each do |h| - h.set_si_weight - h.save - end - end - - desc "January 2015: build Elasticsearch index" - task elasticsearch_create_index: :environment do - Crop.__elasticsearch__.create_index! force: true - Crop.import - end - end # end oneoff section end diff --git a/lib/tasks/openfarm.rake b/lib/tasks/openfarm.rake new file mode 100644 index 000000000..b6b6ce909 --- /dev/null +++ b/lib/tasks/openfarm.rake @@ -0,0 +1,9 @@ +namespace :openfarm do + desc "Retrieve crop info from open farm" + # usage: rake growstuff:admin_user name=skud + + task import: :environment do + Rails.logger = Logger.new(STDOUT) + OpenfarmService.new.import! + end +end diff --git a/spec/controllers/admin/roles_controller_spec.rb b/spec/controllers/admin/roles_controller_spec.rb new file mode 100644 index 000000000..86694a74b --- /dev/null +++ b/spec/controllers/admin/roles_controller_spec.rb @@ -0,0 +1,68 @@ +require 'rails_helper' + +describe Admin::RolesController do + let(:valid_attributes) { { "name" => "MyString" } } + let!(:role) { Role.create! valid_attributes } + + context 'as member' do + login_member(:member) + + describe "GET index" do + before { get :index } + it { expect(assigns(:roles)).to eq(nil) } + end + + describe "GET new" do + before { get :new } + it { expect(assigns(:role)).to eq nil } + end + + describe "create" do + it do + expect { post :create, params: { role: { name: ' new role' } } }.not_to change(Role, :count) + end + end + + describe "GET edit" do + before { get :edit, params: { id: role.to_param } } + it { expect(assigns(:role)).to eq(nil) } + end + + describe "update" do + before { patch :update, params: { id: role.id, role: { name: 'updated role' } } } + it { expect(Role.first.name).to eq 'MyString' } + end + end + + context 'as admin' do + login_member(:admin_member) + + describe "GET index" do + before { get :index } + it { expect(assigns(:roles)).to eq([Role.find_by(name: 'admin'), role]) } + end + + describe "GET new" do + before { get :new } + it { expect(assigns(:role)).to be_a(Role) } + end + + describe "create" do + it do + expect { post :create, params: { role: { name: ' new role' } } }.to change(Role, :count).by(1) + # doesn't allow duplicates + expect { post :create, params: { role: { name: ' new role' } } }.not_to change(Role, :count) + end + end + + describe "GET edit" do + before { get :edit, params: { id: role.to_param } } + it { expect(assigns(:role)).to eq(role) } + end + + describe "update" do + before { patch :update, params: { id: role.id, role: { name: 'updated role' } } } + it { expect(Role.first.name).to eq 'updated role' } + end + end +end diff --git a/spec/controllers/charts/gardens_controller_spec.rb b/spec/controllers/charts/gardens_controller_spec.rb index 1eb959b29..d264388f6 100644 --- a/spec/controllers/charts/gardens_controller_spec.rb +++ b/spec/controllers/charts/gardens_controller_spec.rb @@ -9,7 +9,7 @@ describe Charts::GardensController do describe 'GET timeline' do before { get :timeline, params: { garden_id: garden.to_param } } - it { expect(response).to be_success } + it { expect(response).to be_successful } end end @@ -21,7 +21,7 @@ describe Charts::GardensController do describe 'GET timeline' do before { get :timeline, params: { garden_id: garden.to_param } } - it { expect(response).to be_success } + it { expect(response).to be_successful } end end end diff --git a/spec/controllers/member_controller_spec.rb b/spec/controllers/member_controller_spec.rb index 87a95ccb0..5ec80e344 100644 --- a/spec/controllers/member_controller_spec.rb +++ b/spec/controllers/member_controller_spec.rb @@ -10,31 +10,31 @@ describe MembersController do describe "GET index" do it "assigns only confirmed members as @members" do get :index, params: {} - assigns(:members).should eq([@member]) + expect(assigns(:members)).to eq([@member]) end end describe "GET JSON index" do it "provides JSON for members" do get :index, format: 'json' - response.should be_successful + expect(response).to be_successful end end describe "GET show" do it "provides JSON for member profile" do get :show, params: { slug: @member.to_param }, format: 'json' - response.should be_successful + expect(response).to be_successful end it "assigns @twitter_auth" do get :show, params: { slug: @member.to_param } - assigns(:twitter_auth).should eq(@twitter_auth) + expect(assigns(:twitter_auth)).to eq(@twitter_auth) end it "assigns @flickr_auth" do get :show, params: { slug: @member.to_param } - assigns(:flickr_auth).should eq(@flickr_auth) + expect(assigns(:flickr_auth)).to eq(@flickr_auth) end it "doesn't show completely nonsense members" do @@ -53,9 +53,9 @@ describe MembersController do describe "returns an RSS feed" do before { get :show, params: { slug: @member.to_param }, format: "rss" } - it { response.should be_successful } - it { response.should render_template("members/show") } - it { response.content_type.should eq("application/rss+xml") } + it { expect(response).to be_successful } + it { expect(response).to render_template("members/show") } + it { expect(response.content_type).to eq("application/rss+xml") } end end end diff --git a/spec/controllers/photos_controller_spec.rb b/spec/controllers/photos_controller_spec.rb index c2848f528..7a0e08c7c 100644 --- a/spec/controllers/photos_controller_spec.rb +++ b/spec/controllers/photos_controller_spec.rb @@ -48,8 +48,8 @@ describe PhotosController do describe "planting photos" do before { get :new, params: { type: "planting", id: planting.id } } - it { assigns(:flickr_auth).should be_an_instance_of(Authentication) } - it { assigns(:item).should eq planting } + it { expect(assigns(:flickr_auth)).to be_an_instance_of(Authentication) } + it { expect(assigns(:item)).to eq planting } it { expect(flash[:alert]).not_to be_present } it { expect(flash[:alert]).not_to be_present } end @@ -57,7 +57,7 @@ describe PhotosController do describe "harvest photos" do before { get :new, params: { type: "harvest", id: harvest.id } } - it { assigns(:item).should eq harvest } + it { expect(assigns(:item)).to eq harvest } it { expect(flash[:alert]).not_to be_present } end @@ -79,7 +79,7 @@ describe PhotosController do link_url: "http://example.com") end - let(:member) { FactoryBot.create(:member) } + let(:member) { FactoryBot.create(:member) } let(:garden) { FactoryBot.create(:garden, owner: member) } let(:planting) { FactoryBot.create(:planting, garden: garden, owner: member) } let(:harvest) { FactoryBot.create(:harvest, owner: member) } @@ -88,22 +88,24 @@ describe PhotosController do describe "with valid params" do before { controller.stub(:current_member) { member } } - it "attaches the photo to a planting" do - post :create, params: { - photo: { flickr_photo_id: photo.flickr_photo_id }, - type: "planting", id: planting.id - } - expect(flash[:alert]).not_to be_present - Photo.last.plantings.first.should eq planting + describe "attaches the photo to a planting" do + before do + post :create, params: { + photo: { source_id: photo.source_id, source: 'flickr' }, + type: "planting", id: planting.id + } + end + it { expect(flash[:alert]).not_to be_present } + it { expect(Photo.last.plantings.first).to eq planting } end describe "doesn't attach a photo to a planting twice" do before do post :create, params: { - photo: { flickr_photo_id: photo.flickr_photo_id }, type: "planting", id: planting.id + photo: { source_id: photo.source_id, source: 'flickr' }, type: "planting", id: planting.id } post :create, params: { - photo: { flickr_photo_id: photo.flickr_photo_id }, type: "planting", id: planting.id + photo: { source_id: photo.source_id, source: 'flickr' }, type: "planting", id: planting.id } end @@ -112,27 +114,29 @@ describe PhotosController do end it "attaches the photo to a harvest" do - post :create, params: { photo: { flickr_photo_id: photo.flickr_photo_id }, type: "harvest", id: harvest.id } + post :create, params: { photo: { source_id: photo.source_id, source: 'flickr' }, type: "harvest", id: harvest.id } expect(flash[:alert]).not_to be_present Photo.last.harvests.first.should eq harvest end - it "doesn't attach a photo to a harvest twice" do - post :create, params: { - photo: { flickr_photo_id: photo.flickr_photo_id }, type: "harvest", id: harvest.id - } - post :create, params: { - photo: { flickr_photo_id: photo.flickr_photo_id }, type: "harvest", id: harvest.id - } - expect(flash[:alert]).not_to be_present - Photo.last.harvests.size.should eq 1 + describe "doesn't attach a photo to a harvest twice" do + before do + post :create, params: { + photo: { source_id: photo.source_id, source: 'flickr' }, type: "harvest", id: harvest.id + } + post :create, params: { + photo: { source_id: photo.source_id, source: 'flickr' }, type: "harvest", id: harvest.id + } + end + it { expect(flash[:alert]).not_to be_present } + it { expect(Photo.last.harvests.size).to eq 1 } end it "doesn't attach photo to a comment" do comment = FactoryBot.create(:comment) expect do post :create, params: { - photo: { flickr_photo_id: photo.flickr_photo_id }, type: "comment", id: comment.id + photo: { source_id: photo.source_id, source: 'flickr' }, type: "comment", id: comment.id } end.to raise_error end @@ -140,7 +144,7 @@ describe PhotosController do describe "for the second time" do let(:planting) { FactoryBot.create :planting, owner: member } - let(:valid_params) { { photo: { flickr_photo_id: 1 }, id: planting.id, type: 'planting' } } + let(:valid_params) { { photo: { source_id: 1 }, id: planting.id, type: 'planting' } } it "does not add a photo twice" do expect { post :create, params: valid_params }.to change(Photo, :count).by(1) @@ -151,17 +155,17 @@ describe PhotosController do describe "with matching owners" do before { controller.stub(:current_member) { member } } - it "creates the planting/photo link" do - planting = FactoryBot.create(:planting, garden: garden, owner: member) - photo = FactoryBot.create(:photo, owner: member) - post :create, params: { photo: { flickr_photo_id: photo.flickr_photo_id }, type: "planting", id: planting.id } - expect(flash[:alert]).not_to be_present - expect(Photo.last.plantings.first).to eq planting + describe "creates the planting/photo link" do + let(:planting) { FactoryBot.create(:planting, garden: garden, owner: member) } + let(:photo) { FactoryBot.create(:photo, owner: member) } + before { post :create, params: { photo: { source_id: photo.source_id, source: 'flickr' }, type: "planting", id: planting.id } } + it { expect(flash[:alert]).not_to be_present } + it { expect(Photo.last.plantings.first).to eq planting } end describe "creates the harvest/photo link" do before do - post :create, params: { photo: { flickr_photo_id: photo.flickr_photo_id }, type: "harvest", id: harvest.id } + post :create, params: { photo: { source_id: photo.source_id, source: 'flickr' }, type: "harvest", id: harvest.id } end it { expect(flash[:alert]).not_to be_present } @@ -177,10 +181,11 @@ describe PhotosController do another_planting = FactoryBot.create(:planting) expect do post :create, params: { - photo: { flickr_photo_id: photo.flickr_photo_id }, type: "planting", id: another_planting.id + photo: { source_id: photo.source_id, source: 'flickr' }, + type: "planting", id: another_planting.id } end.to raise_error(ActiveRecord::RecordInvalid) - Photo.last.plantings.first.should_not eq another_planting + expect(Photo.last.plantings.first).not_to eq another_planting end it "does not create the harvest/photo link" do @@ -188,10 +193,10 @@ describe PhotosController do another_harvest = FactoryBot.create(:harvest) expect do post :create, params: { - photo: { flickr_photo_id: photo.flickr_photo_id }, type: "harvest", id: another_harvest.id + photo: { source_id: photo.source_id, source: 'flickr' }, type: "harvest", id: another_harvest.id } end.to raise_error(ActiveRecord::RecordInvalid) - Photo.last.harvests.first.should_not eq another_harvest + expect(Photo.last.harvests.first).not_to eq another_harvest end end end diff --git a/spec/controllers/roles_controller_spec.rb b/spec/controllers/roles_controller_spec.rb deleted file mode 100644 index 17fe48dfe..000000000 --- a/spec/controllers/roles_controller_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'rails_helper' - -describe RolesController do - def valid_attributes - { "name" => "MyString" } - end - - login_member(:admin_member) - - describe "GET index" do - it "assigns all roles as @roles" do - role = Role.create! valid_attributes - get :index, params: {} - # note that admin role exists because of login_admin_member - assigns(:roles).should eq([Role.find_by(name: 'admin'), role]) - end - end -end diff --git a/spec/factories/crop_companions.rb b/spec/factories/crop_companions.rb new file mode 100644 index 000000000..6db4f0eb0 --- /dev/null +++ b/spec/factories/crop_companions.rb @@ -0,0 +1,4 @@ +FactoryBot.define do + factory :crop_companion do + end +end diff --git a/spec/factories/member.rb b/spec/factories/member.rb index a8ec81024..37ed2c185 100644 --- a/spec/factories/member.rb +++ b/spec/factories/member.rb @@ -7,7 +7,7 @@ FactoryBot.define do confirmed_at { Time.zone.now } show_email { false } bio { 'I love seeds' } - preferred_avatar_uri { 'http://www.gravatar.com/avatar/d021434aac03a7f7c7c0de60d07dad1c?size=150&default=identicon' } + preferred_avatar_uri { 'https://secure.gravatar.com/avatar/d021434aac03a7f7c7c0de60d07dad1c?size=150&default=identicon' } # cropbot is needed for certain tests, eg. Crop.create_from_csv factory :cropbot do diff --git a/spec/factories/photos.rb b/spec/factories/photos.rb index b0a64331e..41e581d29 100644 --- a/spec/factories/photos.rb +++ b/spec/factories/photos.rb @@ -3,7 +3,8 @@ FactoryBot.define do factory :photo do owner - flickr_photo_id { 1 } + source { 'flickr' } + source_id { 1 } title { Faker::Movies::HarryPotter.quote } license_name { "CC-BY" } license_url { "http://example.com/license.html" } @@ -13,7 +14,7 @@ FactoryBot.define do factory :unlicensed_photo do license_name { "All rights reserved" } - license_url { "" } + license_url { nil } end end end diff --git a/spec/factories/planting.rb b/spec/factories/planting.rb index 24c092f82..8fb709d1c 100644 --- a/spec/factories/planting.rb +++ b/spec/factories/planting.rb @@ -38,5 +38,26 @@ FactoryBot.define do planted_at { Time.zone.local(2014, 7, 30) } finished_at { Time.zone.local(2014, 8, 30) } end + + factory :annual_planting do + crop { FactoryBot.create :annual_crop } + end + + factory :perennial_planting do + crop { FactoryBot.create :perennial_crop } + end + + factory :predicatable_planting do + crop do + crop = FactoryBot.create :annual_crop + FactoryBot.create :planting, crop: crop, planted_at: 10.days.ago + FactoryBot.create :planting, crop: crop, planted_at: 100.days.ago, finished_at: 50.days.ago + FactoryBot.create :planting, crop: crop, planted_at: 100.days.ago, finished_at: 51.days.ago + FactoryBot.create :planting, crop: crop, planted_at: 2.years.ago, finished_at: 50.days.ago + FactoryBot.create :planting, crop: crop, planted_at: 150.days.ago, finished_at: 100.days.ago + crop.update_lifespan_medians + crop + end + end end end diff --git a/spec/features/conversations/index_spec.rb b/spec/features/conversations/index_spec.rb index 850e42236..969fc447c 100644 --- a/spec/features/conversations/index_spec.rb +++ b/spec/features/conversations/index_spec.rb @@ -20,8 +20,8 @@ describe "Conversations", :js do describe 'deleting' do before do - # delete button - click_link class: 'delete' + check 'conversation_ids[]' + click_button 'Delete Selected' end describe 'view trash' do @@ -39,4 +39,41 @@ describe "Conversations", :js do end end end + + describe 'deleting conversations' do + it 'deletes multiple conversations from the inbox' do + sender.send_message(recipient, 'this is a message', 'message 1') + sender.send_message(recipient, 'this is another message', 'follow up message') + + visit root_path + click_link 'Your Stuff' + click_link 'Inbox' + + all('input[type=checkbox]').each(&:click) + click_button 'Delete Selected' + + expect(page).not_to have_content 'this is a message' + expect(page).not_to have_content 'this is another message' + end + + it 'deletes multiple conversations from the sentbox' do + sender.send_message(recipient, 'this is a message', 'message 1') + sender.send_message(recipient, 'this is another message', 'follow up message') + + visit root_path + click_link 'Your Stuff' + click_link 'Inbox' + + expect(page).to have_selector('.sent') + find('.sent').click + + all('input[type=checkbox]').each(&:click) + click_button 'Delete Selected' + + expect(page).to have_selector('.sent') + find('.sent').click + expect(page).not_to have_content 'this is a message' + expect(page).not_to have_content 'this is another message' + end + end end diff --git a/spec/features/crops/creating_a_crop_spec.rb b/spec/features/crops/creating_a_crop_spec.rb index 21178ac92..6240db8d5 100644 --- a/spec/features/crops/creating_a_crop_spec.rb +++ b/spec/features/crops/creating_a_crop_spec.rb @@ -7,14 +7,14 @@ describe "Crop", js: true do within "form#new_crop" do fill_in "crop_name", with: "Philippine flower" fill_in "en_wikipedia_url", with: "https://en.wikipedia.org/wiki/Jasminum_sambac" - click_button "add-sci_name-row" + click_button class: "add-sciname-row" fill_in "sci_name[1]", with: "Jasminum sambac 1" fill_in "sci_name[2]", with: "Jasminum sambac 2" fill_in "alt_name[1]", with: "Sampaguita" - click_button "add-alt_name-row" - click_button "add-alt_name-row" + click_button class: "add-altname-row" + click_button class: "add-altname-row" fill_in "alt_name[2]", with: "Manol" - click_button "add-alt_name-row" + click_button class: "add-altname-row" fill_in "alt_name[3]", with: "Jazmin" fill_in "alt_name[4]", with: "Matsurika" end diff --git a/spec/features/crops/crop_detail_page_spec.rb b/spec/features/crops/crop_detail_page_spec.rb index 3d1449a1d..c06a03de0 100644 --- a/spec/features/crops/crop_detail_page_spec.rb +++ b/spec/features/crops/crop_detail_page_spec.rb @@ -1,6 +1,9 @@ require 'rails_helper' describe "crop detail page", js: true do + before do + FactoryBot.create :plant_part, name: 'leaf' + end subject do # Update the medians after all the # data has been loaded @@ -26,13 +29,16 @@ describe "crop detail page", js: true do before { subject } it "has a link to plant the crop" do - expect(page).to have_link "Plant #{crop.name}", href: new_planting_path(crop_id: crop.id) + click_link 'Add to garden' + expect(page).to have_link "add new garden" end it "has a link to harvest the crop" do - expect(page).to have_link "Harvest #{crop.name}", href: new_harvest_path(crop_id: crop.id) + click_link 'Record harvest' + expect(page).to have_link "leaf" end it "has a link to add seeds" do - expect(page).to have_link "Add #{crop.name} seeds to stash", href: new_seed_path(crop_id: crop.id) + click_link 'Save seeds' + expect(page).to have_link "Will trade: nowhere" end end @@ -61,8 +67,7 @@ describe "crop detail page", js: true do end it "has a link to OpenFarm" do - expect(page).to have_link "OpenFarm - Growing guide", - href: "https://openfarm.cc/en/crops/#{CGI.escape crop.name}" + expect(page).to have_link "OpenFarm - Growing guide" end it "has a link to gardenate" do @@ -99,19 +104,19 @@ describe "crop detail page", js: true do describe 'with harvest history data' do before do # 50 days to harvest - FactoryBot.create(:harvest, harvested_at: 150.days.ago, crop: planting.crop, + FactoryBot.create(:harvest, harvested_at: 150.days.ago, crop: crop, planting: FactoryBot.create(:planting, planted_at: 200.days.ago, crop: crop)) # 20 days to harvest - FactoryBot.create(:harvest, harvested_at: 180.days.ago, crop: planting.crop, + FactoryBot.create(:harvest, harvested_at: 180.days.ago, crop: crop, planting: FactoryBot.create(:planting, planted_at: 200.days.ago, crop: crop)) # 10 days to harvest - FactoryBot.create(:harvest, harvested_at: 190.days.ago, crop: planting.crop, + FactoryBot.create(:harvest, harvested_at: 190.days.ago, crop: crop, planting: FactoryBot.create(:planting, planted_at: 200.days.ago, crop: crop)) - planting.crop.update_medians + crop.update_medians end it "predicts harvest" do - expect(subject).to have_text("First harvest expected 20 days after planting") + expect(subject).to have_text("First harvest expected 3 weeks after planting") end end end @@ -123,29 +128,25 @@ describe "crop detail page", js: true do finished_at: 1.day.ago) end - context 'crop is an annual' do + context 'crop is an Annual' do let(:crop) { FactoryBot.create(:annual_crop) } - describe 'with no harvests' do - end - describe 'with harvests' do include_examples "predicts harvest" end it "predicts lifespan" do expect(subject).to have_text "Median lifespan" - expect(subject).to have_text "99 days" + expect(subject).to have_text "14 weeks" end - it "describes annual crops" do - expect(subject).to have_text( - "#{crop.name.capitalize} is an annual crop (living and reproducing in a single year or less)" - ) + it "describes Annual crops" do + expect(subject).to have_text("living and reproducing in a single year or less") + expect(subject).to have_text('Annual') end end - context 'crop is perennial' do + context 'crop is Perennial' do let(:crop) { FactoryBot.create :perennial_crop } describe 'with no harvests' do @@ -155,12 +156,13 @@ describe "crop detail page", js: true do include_examples "predicts harvest" end - it "describes perennial crops" do - expect(subject).to have_text("#{crop.name.capitalize} is a perennial crop (living more than two years)") + it "describes Perennial crops" do + expect(subject).to have_text("Perennial") + expect(subject).to have_text("living more than two years") end end - context 'crop perennial value is null' do + context 'crop Perennial value is null' do let(:crop) { FactoryBot.create :crop, perennial: nil } describe 'with no harvests' do @@ -172,28 +174,30 @@ describe "crop detail page", js: true do end end - context 'annual and perennial' do + context 'Annual and Perennial' do before { visit crop_path(crop) } - context 'crop is an annual' do + context 'crop is an Annual' do let(:crop) { FactoryBot.create :annual_crop } - it { expect(page).to have_text 'annual crop (living and reproducing in a single year or less)' } - it { expect(page).not_to have_text 'perennial crop (living more than two years)' } + it { expect(page).to have_text 'Annual' } + it { expect(page).to have_text 'living and reproducing in a single year or less' } + it { expect(page).not_to have_text 'Perennial' } end - context 'crop is perennial' do + context 'crop is Perennial' do let(:crop) { FactoryBot.create :perennial_crop } - it { expect(page).to have_text 'perennial crop (living more than two years)' } - it { expect(page).not_to have_text 'annual crop (living and reproducing in a single year or less)' } + it { expect(page).to have_text 'Perennial' } + it { expect(page).to have_text 'living more than two years' } + it { expect(page).not_to have_text 'Annual' } end - context 'crop perennial value is null' do + context 'crop Perennial value is null' do let(:crop) { FactoryBot.create :crop, perennial: nil } - it { expect(page).not_to have_text 'perennial crop (living more than two years)' } - it { expect(page).not_to have_text 'annual crop (living and reproducing in a single year or less)' } + it { expect(page).not_to have_text 'Perennial' } + it { expect(page).not_to have_text 'Annual' } end end end diff --git a/spec/features/crops/request_new_crop_spec.rb b/spec/features/crops/request_new_crop_spec.rb index 4257e3a69..da38b1ec9 100644 --- a/spec/features/crops/request_new_crop_spec.rb +++ b/spec/features/crops/request_new_crop_spec.rb @@ -20,19 +20,17 @@ describe "Requesting a new crop" do it "Approve a request" do visit edit_crop_path(crop) - select "approved", from: "Approval status" - click_button "Save" + click_button "Approve and save" expect(page).to have_content "En wikipedia url is not a valid English Wikipedia URL" fill_in "en_wikipedia_url", with: "http://en.wikipedia.org/wiki/Aung_San_Suu_Kyi" - click_button "Save" + click_button "Approve and save" expect(page).to have_content "crop was successfully updated." end it "Rejecting a crop" do visit edit_crop_path(crop) - select "rejected", from: "Approval status" select "not edible", from: "Reason for rejection" - click_button "Save" + click_button "Reject" expect(page).to have_content "crop was successfully updated." end end diff --git a/spec/features/crops/show_spec.rb b/spec/features/crops/show_spec.rb index f6fb15410..48d083257 100644 --- a/spec/features/crops/show_spec.rb +++ b/spec/features/crops/show_spec.rb @@ -1,13 +1,23 @@ require 'rails_helper' describe "browse crops" do - let(:tomato) { create :tomato } + let(:tomato) { FactoryBot.create :tomato } it "Show crop info" do visit crop_path(tomato) expect(page).to have_text 'tomato' end + describe "shows varieties" do + let!(:cherry) { FactoryBot.create :crop, name: 'cherry tomato', parent: tomato } + let!(:heirloom) { FactoryBot.create :crop, name: 'heirloom tomato', parent: tomato } + let!(:striped) { FactoryBot.create :crop, name: 'striped tomato', parent: heirloom } + before { visit crop_path(tomato) } + it { expect(page).to have_link heirloom.name, href: crop_path(heirloom) } + it { expect(page).to have_link cherry.name, href: crop_path(cherry) } + it { expect(page).to have_link striped.name, href: crop_path(striped) } + end + context "when the most recently created harvest is not the most recently harvested" do before { FactoryBot.create_list :harvest, 20, crop: tomato, harvested_at: 1.year.ago, created_at: 1.minute.ago } diff --git a/spec/features/gardens/actions_spec.rb b/spec/features/gardens/actions_spec.rb index 0c9336a74..0dbfe1882 100644 --- a/spec/features/gardens/actions_spec.rb +++ b/spec/features/gardens/actions_spec.rb @@ -12,7 +12,7 @@ describe "Gardens" do describe '#index' do shared_examples "has buttons bar at top" do it "has buttons bar at top" do - within '.layout-actions' do + within '.nav' do expect(subject).to have_link 'Add a garden' expect(subject).to have_link 'My gardens' expect(subject).to have_link "Everyone's gardens" diff --git a/spec/features/gardens/gardens_spec.rb b/spec/features/gardens/gardens_spec.rb index 132997584..6b845bc50 100644 --- a/spec/features/gardens/gardens_spec.rb +++ b/spec/features/gardens/gardens_spec.rb @@ -12,13 +12,9 @@ describe "Planting a crop", js: true do it "View gardens" do visit gardens_path expect(page).to have_content "Everyone's gardens" - within '.layout-actions' do - click_link "My gardens" - end + click_link "My gardens" expect(page).to have_content "#{garden.owner.login_name}'s gardens" - within '.layout-actions' do - click_link "Everyone's gardens" - end + click_link "Everyone's gardens" expect(page).to have_content "Everyone's gardens" end diff --git a/spec/features/gardens/index_spec.rb b/spec/features/gardens/index_spec.rb index 526bba7c9..ba3d137d2 100644 --- a/spec/features/gardens/index_spec.rb +++ b/spec/features/gardens/index_spec.rb @@ -103,8 +103,7 @@ describe "Gardens#index", :js do it { expect(page).to have_link href: planting_path(planting) } it { expect(page).to have_link href: garden_path(planting.garden) } - it { expect(page).to have_text '50 days' } - it { expect(page).to have_text '90 days' } + it { expect(page).to have_text '7 weeks' } it { expect(page).not_to have_text 'harvesting now' } end @@ -120,8 +119,7 @@ describe "Gardens#index", :js do it { expect(crop.median_lifespan).to eq 90 } it { expect(page).to have_text 'harvesting now' } - it { expect(page).to have_text '39 days' } - it { expect(page).not_to have_text 'Predicted days until harvest' } + it { expect(page).not_to have_text 'Predicted weeks until harvest' } end describe 'super late' do @@ -133,8 +131,8 @@ describe "Gardens#index", :js do it { expect(page).to have_text 'super late' } it { expect(page).not_to have_text 'harvesting now' } - it { expect(page).not_to have_text 'Predicted days until harvest' } - it { expect(page).not_to have_text 'Predicted days until planting is finished' } + it { expect(page).not_to have_text 'Predicted weeks until harvest' } + it { expect(page).not_to have_text 'Predicted weeks until planting is finished' } end end end diff --git a/spec/features/harvests/harvesting_a_crop_spec.rb b/spec/features/harvests/harvesting_a_crop_spec.rb index e7aeea3d6..a3261525a 100644 --- a/spec/features/harvests/harvesting_a_crop_spec.rb +++ b/spec/features/harvests/harvesting_a_crop_spec.rb @@ -47,14 +47,8 @@ describe "Harvesting a crop", :js, :elasticsearch do describe "Harvesting from crop page" do before do visit crop_path(maize) - within '.crop-actions' do - click_link "Harvest #{maize.name}" - end - within "form#new_harvest" do - choose plant_part.name - expect(page).to have_selector "input[value='maize']" - click_button "Save" - end + click_link 'Record harvest' + click_link plant_part.name end it { expect(page).to have_content "harvest was successfully created." } @@ -65,10 +59,8 @@ describe "Harvesting a crop", :js, :elasticsearch do let!(:planting) { create :planting, crop: maize, owner: member, garden: member.gardens.first } before do visit planting_path(planting) - click_link "Record Harvest" - - choose plant_part.name - click_button "Save" + click_link "Record harvest" + click_link plant_part.name end it { expect(page).to have_content "harvest was successfully created." } diff --git a/spec/features/percy/percy_spec.rb b/spec/features/percy/percy_spec.rb index 70ce97092..529f8f9a1 100644 --- a/spec/features/percy/percy_spec.rb +++ b/spec/features/percy/percy_spec.rb @@ -9,10 +9,10 @@ describe 'Test with visual testing', type: :feature, js: true do let!(:admin_user) { FactoryBot.create :admin_member, login_name: 'janitor', preferred_avatar_uri: gravatar3 } let!(:someone_else) { FactoryBot.create :edinburgh_member, login_name: 'ruby', preferred_avatar_uri: gravatar4 } - let(:gravatar) { 'http://www.gravatar.com/avatar/d021434aac03a7f7c7c0de60d07dad1c?size=150&default=identicon' } - let(:gravatar2) { 'http://www.gravatar.com/avatar/353d83d3677b142520987e1936fd093c?size=150&default=identicon' } - let(:gravatar3) { 'http://www.gravatar.com/avatar/622db62c7beab8d5d8b7a80aa6385b2f?size=150&default=identicon' } - let(:gravatar4) { 'http://www.gravatar.com/avatar/7fd767571ff5ceefc7a687a543b2c402?size=150&default=identicon' } + let(:gravatar) { 'https://secure.gravatar.com/avatar/d021434aac03a7f7c7c0de60d07dad1c?size=150&default=identicon' } + let(:gravatar2) { 'https://secure.gravatar.com/avatar/353d83d3677b142520987e1936fd093c?size=150&default=identicon' } + let(:gravatar3) { 'https://secure.gravatar.com/avatar/622db62c7beab8d5d8b7a80aa6385b2f?size=150&default=identicon' } + let(:gravatar4) { 'https://secure.gravatar.com/avatar/7fd767571ff5ceefc7a687a543b2c402?size=150&default=identicon' } let!(:tomato) { FactoryBot.create :tomato, creator: someone_else } let(:plant_part) { FactoryBot.create :plant_part, name: 'fruit' } diff --git a/spec/features/planting_reminder_spec.rb b/spec/features/planting_reminder_spec.rb index 1b8fe7a37..5caecbc51 100644 --- a/spec/features/planting_reminder_spec.rb +++ b/spec/features/planting_reminder_spec.rb @@ -21,45 +21,43 @@ describe "Planting reminder email", :js do end it "doesn't list plantings" do - expect(mail).not_to have_content "most recent plantings you've told us about" + expect(mail).not_to have_content "Progress report" end end context "when member has some plantings" do # Bangs are used on the following 2 let blocks in order to ensure that the plantings are present # in the database before the email is generated: otherwise, they won't be present in the email. - let!(:p1) { create :planting, garden: member.gardens.first, owner: member } - let!(:p2) { create :planting, garden: member.gardens.first, owner: member } + let!(:p1) { FactoryBot.create :predicatable_planting, planted_at: 10.days.ago, garden: member.gardens.first, owner: member } + let!(:p2) { FactoryBot.create :predicatable_planting, planted_at: 30.days.ago, garden: member.gardens.first, owner: member } - it "lists plantings" do - expect(mail).to have_content "most recent plantings you've told us about" - expect(mail).to have_link p1.to_s, href: planting_url(p1) - expect(mail).to have_link p2.to_s, href: planting_url(p2) - expect(mail).to have_content "keep your garden records up to date" + describe "lists plantings" do + it { expect(mail).to have_content "Progress report" } + it { expect(mail).to have_link p1.crop.to_s, href: planting_url(p1) } + it { expect(mail).to have_link p2.crop.to_s, href: planting_url(p2) } + it { expect(mail).to have_content "keep your garden records up to date" } end end context "when member has no harvests" do - it "tells you to tracking plantings" do - expect(mail).to have_content "Get started now by tracking your first harvest" - end - it "doesn't list plantings" do - expect(mail).not_to have_content "the last few things you harvested were" + expect(mail).not_to have_content "Ready to harvest" end end context "when member has some harvests" do # Bangs are used on the following 2 let blocks in order to ensure that the plantings are present # in the database before the spec is run. - let!(:h1) { create :harvest, owner: member } - let!(:h2) { create :harvest, owner: member } + let!(:p1) { FactoryBot.create :predicatable_planting, garden: member.gardens.first, owner: member, planted_at: 20.days.ago } + let!(:p2) { FactoryBot.create :predicatable_planting, garden: member.gardens.first, owner: member } + let!(:h1) { FactoryBot.create :harvest, owner: member, planting: p1, harvested_at: 1.day.ago } + let!(:h2) { FactoryBot.create :harvest, owner: member, planting: p2, harvested_at: 3.days.ago } - it "lists harvests" do - expect(mail).to have_content "the last few things you harvested were" - expect(mail).to have_link h1.to_s, href: harvest_url(h1) - expect(mail).to have_link h2.to_s, href: harvest_url(h2) - expect(mail).to have_content "Harvested anything else lately?" + describe "lists planting that are ready for harvest" do + it { expect(mail).to have_content "Ready to harvest" } + it { expect(mail).to have_link p1.crop.name, href: planting_url(p1) } + it { expect(mail).to have_link p2.crop.name, href: planting_url(p2) } + it { expect(mail).to have_content "Harvested anything lately?" } end end end diff --git a/spec/features/plantings/planting_a_crop_spec.rb b/spec/features/plantings/planting_a_crop_spec.rb index 5a262ecbb..cd6fb2b3a 100644 --- a/spec/features/plantings/planting_a_crop_spec.rb +++ b/spec/features/plantings/planting_a_crop_spec.rb @@ -2,8 +2,8 @@ require "rails_helper" require 'custom_matchers' describe "Planting a crop", :js, :elasticsearch do - let!(:maize) { FactoryBot.create :maize } - let(:garden) { FactoryBot.create :garden, owner: member } + let!(:maize) { FactoryBot.create :maize } + let(:garden) { FactoryBot.create :garden, owner: member, name: 'Orchard' } let!(:planting) do FactoryBot.create :planting, garden: garden, owner: member, planted_at: Date.parse("2013-03-10") end @@ -116,7 +116,7 @@ describe "Planting a crop", :js, :elasticsearch do expect(page).to have_content "planting was successfully created" expect(page).not_to have_content "0%" - expect(page).not_to have_content "Finished" + expect(page).not_to have_content "Finish expected" expect(page).not_to have_content "Finishes" end @@ -160,16 +160,8 @@ describe "Planting a crop", :js, :elasticsearch do it "Planting from crop page" do visit crop_path(maize) - within '.crop-actions' do - click_link "Plant maize" - end - within "form#new_planting" do - expect(page).to have_selector "input[value='maize']" - end - - choose(member.gardens.first.name) - click_button "Save" - + click_link "Add to garden" + click_link "Orchard" expect(page).to have_content "planting was successfully created" expect(page).to have_content "maize" end @@ -224,7 +216,7 @@ describe "Planting a crop", :js, :elasticsearch do end expect(page).to have_content "planting was successfully created" expect(page).to have_content "Finished" - expect(page).to have_content "30 Aug" + expect(page).to have_content "Aug 2014" # shouldn't be on the page visit plantings_path diff --git a/spec/features/plantings/show_spec.rb b/spec/features/plantings/show_spec.rb new file mode 100644 index 000000000..9d8450834 --- /dev/null +++ b/spec/features/plantings/show_spec.rb @@ -0,0 +1,82 @@ +require "rails_helper" +require 'custom_matchers' + +describe "Display a planting", :js, :elasticsearch do + context 'anonymous' do + before { visit planting_path(planting) } + + context 'Perennial planted long ago' do + let(:planting) { FactoryBot.create :perennial_planting } + it { expect(page).to have_text 'Perennial' } + end + + context 'Perennial finished' do + let(:planting) { FactoryBot.create :perennial_planting, planted_at: 6.years.ago, finished: true, finished_at: 1.year.ago } + it { expect(page).to have_text 'Perennial' } + end + + context 'Annual no predictions' do + let(:planting) { FactoryBot.create :annual_planting } + it { expect(page).not_to have_text 'Finish expected' } + end + + context 'Annual with predicted finish' do + let(:planting) { FactoryBot.create :predicatable_planting, planted_at: 2.weeks.ago } + it { expect(page).to have_text '28%' } + it { expect(page).to have_text "Planted #{I18n.l(2.weeks.ago.to_date)}" } + it { expect(page).to have_text 'Finish expected' } + end + + context 'Annual finished' do + let(:planting) { FactoryBot.create :annual_planting, planted_at: 100.days.ago, finished: true, finished_at: 1.day.ago } + it { expect(page).to have_text "Planted #{I18n.l(planting.planted_at)}" } + end + + context 'Planting with harvests' do + let(:planting) { FactoryBot.create(:harvest_with_planting).planting } + it { expect(page).to have_text 'Harvest started' } + end + + context 'Planting with harvest predictable' do + let(:planting) do + crop = FactoryBot.create :annual_crop + # 50 days to harvest + FactoryBot.create(:harvest, harvested_at: 150.days.ago, crop: crop, + planting: FactoryBot.create(:planting, planted_at: 200.days.ago, crop: crop)) + # 20 days to harvest + FactoryBot.create(:harvest, harvested_at: 180.days.ago, crop: crop, + planting: FactoryBot.create(:planting, planted_at: 200.days.ago, crop: crop)) + # 10 days to harvest + FactoryBot.create(:harvest, harvested_at: 190.days.ago, crop: crop, + planting: FactoryBot.create(:planting, planted_at: 200.days.ago, crop: crop)) + crop.update_medians + + FactoryBot.create :annual_planting, planted_at: 200.days.ago, crop: crop + end + it { expect(page).to have_text 'First harvest expected' } + end + + context 'with quantity' do + let(:planting) { FactoryBot.create :planting, quantity: 100 } + it { expect(find('.plantingfact--quantity')).to have_text '100' } + end + end + context 'signed in' do + include_context 'signed in member' + before { visit planting_path(planting) } + + context 'with matching seeds' do + let(:seed) { FactoryBot.create :seed, saved_at: 1.month.ago, owner: member } + let(:planting) { FactoryBot.create :planting, planted_at: 1.day.ago, crop: seed.crop, owner: member } + it { expect(page).to have_text 'Is this from one of these plantings? ' } + describe 'linking to planting' do + before do + choose "planting_parent_seed_id_#{planting.id}" + click_button 'save' + end + it { expect(page).to have_text 'Parent seed' } + it { expect(page).to have_link href: planting_path(planting) } + end + end + end +end diff --git a/spec/features/seeds/adding_seeds_spec.rb b/spec/features/seeds/adding_seeds_spec.rb index dd667bdb7..6ec0b1bd3 100644 --- a/spec/features/seeds/adding_seeds_spec.rb +++ b/spec/features/seeds/adding_seeds_spec.rb @@ -46,22 +46,19 @@ describe "Seeds", :js, :elasticsearch do end it { expect(page).to have_content "Successfully added maize seed to your stash" } - it { expect(page).to have_content "Quantity: 42" } - it { expect(page).to have_content "Days until maturity: 999–1999" } - it { expect(page).to have_content "certified organic" } - it { expect(page).to have_content "non-certified GMO-free" } - it { expect(page).to have_content "Heirloom? heirloom" } - it { expect(page).to have_content "It's killer." } + it { expect(find('.seedfacts--quantity')).to have_content "42" } + it { expect(find('.seedfacts--maturity')).to have_content "999–1999" } + it { expect(find('.seedtitle--organic')).to have_content "certified organic" } + it { expect(find('.seedtitle--gmo')).to have_content "non-certified GMO-free" } + it { expect(find('.seedtitle--heirloom')).to have_content "heirloom" } + it { expect(find('.seed--description')).to have_content "It's killer." } end describe "Adding a seed from crop page" do before do visit crop_path(maize) - click_link "Add maize seeds to stash" - within "form#new_seed" do - expect(page).to have_selector "input[value='maize']" - click_button "Save" - end + click_link "Save seeds" + click_link "Will trade: nowhere" end it { expect(page).to have_content "Successfully added maize seed to your stash" } diff --git a/spec/features/seeds/misc_seeds_spec.rb b/spec/features/seeds/misc_seeds_spec.rb index b2205ee8a..a1d047daf 100644 --- a/spec/features/seeds/misc_seeds_spec.rb +++ b/spec/features/seeds/misc_seeds_spec.rb @@ -19,6 +19,7 @@ describe "seeds", js: true do describe "button on front page to add seeds" do before do visit root_path + click_link 'Record' click_link(href: new_seed_path) end @@ -29,7 +30,7 @@ describe "seeds", js: true do describe "Clicking link to owner's profile" do before do visit member_seeds_path(member) - click_link "View #{member}'s profile >>" + click_link "View Owners Profile" end it { expect(current_path).to eq member_path(member) } @@ -68,25 +69,25 @@ describe "seeds", js: true do describe "view seeds with max and min days until maturity" do let(:seed) { FactoryBot.create :seed, days_until_maturity_min: 5, days_until_maturity_max: 7 } - it { expect(page).to have_content "Days until maturity: 5–7" } + it { expect(find('.seedfacts--maturity')).to have_content("5–7") } end describe "view seeds with only max days until maturity" do let(:seed) { FactoryBot.create :seed, days_until_maturity_max: 7 } - it { expect(page).to have_content "Days until maturity: 7" } + it { expect(find('.seedfacts--maturity')).to have_content("7") } end describe "view seeds with only min days until maturity" do let(:seed) { FactoryBot.create :seed, days_until_maturity_min: 5 } - it { expect(page).to have_content "Days until maturity: 5" } + it { expect(find('.seedfacts--maturity')).to have_content("5") } end describe "view seeds with neither max nor min days until maturity" do let(:seed) { FactoryBot.create :seed } - it { expect(page).to have_content "Days until maturity: unknown" } + it { expect(find('.seedfacts--maturity')).to have_content "unknown" } end end end diff --git a/spec/features/shared_examples/append_date.rb b/spec/features/shared_examples/append_date.rb index 8dc283c22..2680dac30 100644 --- a/spec/features/shared_examples/append_date.rb +++ b/spec/features/shared_examples/append_date.rb @@ -1,5 +1,6 @@ shared_examples "append date" do let(:this_month) { Time.zone.today.strftime("%b") } + let(:this_year) { Time.zone.today.year } before { visit path } @@ -13,7 +14,7 @@ shared_examples "append date" do end end it { expect(page).to have_content "Finished" } - it { expect(page).to have_content "21 #{this_month}" } + it { expect(page).to have_content "#{this_month} #{this_year}" } end describe "Confirming without selecting date" do diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 353e6e9b0..ae7230a00 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -22,11 +22,11 @@ describe ApplicationHelper do end it 'renders a gravatar uri' do - expect(avatar_uri(@member)).to eq 'http://www.gravatar.com/avatar/23463b99b62a72f26ed677cc556c44e8?size=150&default=identicon' + expect(avatar_uri(@member)).to eq 'https://secure.gravatar.com/avatar/23463b99b62a72f26ed677cc556c44e8?size=150&default=identicon' end it 'renders a gravatar uri for a given size' do - expect(avatar_uri(@member, 456)).to eq 'http://www.gravatar.com/avatar/23463b99b62a72f26ed677cc556c44e8?size=456&default=identicon' + expect(avatar_uri(@member, 456)).to eq 'https://secure.gravatar.com/avatar/23463b99b62a72f26ed677cc556c44e8?size=456&default=identicon' end end diff --git a/spec/helpers/event_helper_spec.rb b/spec/helpers/event_helper_spec.rb index 2ed681901..f90a04c11 100644 --- a/spec/helpers/event_helper_spec.rb +++ b/spec/helpers/event_helper_spec.rb @@ -44,4 +44,16 @@ RSpec.describe EventHelper, type: :helper do it { expect(subject).to eq photo } it { expect(event_description(event)).to have_text "took a photo" } end + + describe 'in_weeks' do + it { expect(in_weeks(14)).to eq 2 } + it { expect(in_weeks(15)).to eq 2 } + it { expect(in_weeks(16)).to eq 2 } + it { expect(in_weeks(17)).to eq 2 } + it { expect(in_weeks(18)).to eq 3 } + it { expect(in_weeks(19)).to eq 3 } + it { expect(in_weeks(20)).to eq 3 } + it { expect(in_weeks(21)).to eq 3 } + it { expect(in_weeks(22)).to eq 3 } + end end diff --git a/spec/helpers/photos_helper_spec.rb b/spec/helpers/photos_helper_spec.rb index efc6ef07e..b24a254c7 100644 --- a/spec/helpers/photos_helper_spec.rb +++ b/spec/helpers/photos_helper_spec.rb @@ -2,16 +2,16 @@ require 'rails_helper' describe PhotosHelper do let(:crop) { FactoryBot.create :crop } + let(:crop_photo_of) { FactoryBot.create(:photo, source: 'openfarm') } + let(:crop_photo_flickr) { FactoryBot.create(:photo, source: 'flickr') } let(:garden) { FactoryBot.create :garden } - - let(:garden_photo) { FactoryBot.create(:photo, thumbnail_url: 'garden.jpg', owner: garden.owner) } - let(:planting) { FactoryBot.create :planting, crop: crop, owner: garden.owner } - let(:planting_photo) { FactoryBot.create(:photo, thumbnail_url: 'planting.jpg', owner: garden.owner) } - let(:harvest) { FactoryBot.create :harvest, crop: crop, owner: garden.owner } - let(:harvest_photo) { FactoryBot.create(:photo, thumbnail_url: 'harvest.jpg', owner: garden.owner) } - let(:seed) { FactoryBot.create :seed, crop: crop, owner: garden.owner } - let(:seed_photo) { FactoryBot.create(:photo, thumbnail_url: 'seed.jpg', owner: garden.owner) } + let(:planting) { FactoryBot.create :planting, crop: crop, owner: garden.owner } + let(:planting_photo) { FactoryBot.create(:photo, owner: garden.owner) } + let(:harvest) { FactoryBot.create :harvest, crop: crop, owner: garden.owner } + let(:harvest_photo) { FactoryBot.create(:photo, owner: garden.owner) } + let(:seed) { FactoryBot.create :seed, crop: crop, owner: garden.owner } + let(:seed_photo) { FactoryBot.create(:photo, owner: garden.owner) } describe "crops" do subject { crop_image_path(crop) } @@ -22,7 +22,7 @@ describe PhotosHelper do before { planting.photos << planting_photo } it "uses planting photos" do - expect(subject).to eq planting_photo.thumbnail_url + expect(subject).to eq planting_photo.fullsize_url end end @@ -30,7 +30,7 @@ describe PhotosHelper do before { harvest.photos << harvest_photo } it "uses harvest photos" do - expect(subject).to eq harvest_photo.thumbnail_url + expect(subject).to eq harvest_photo.fullsize_url end end @@ -38,7 +38,7 @@ describe PhotosHelper do before { seed.photos << seed_photo } it "uses seed photos" do - expect(subject).to eq seed_photo.thumbnail_url + expect(subject).to eq seed_photo.fullsize_url end end end @@ -48,10 +48,10 @@ describe PhotosHelper do it { is_expected.to eq 'placeholder_600.png' } - describe "uses garden's own photo" do + describe "has a flickr photo" do + let(:garden_photo) { FactoryBot.create(:photo, owner: garden.owner, source: 'flickr') } before { garden.photos << garden_photo } - - it { is_expected.to eq garden_photo.thumbnail_url } + it { is_expected.to eq garden_photo.fullsize_url } end end @@ -59,10 +59,11 @@ describe PhotosHelper do subject { planting_image_path(planting) } it { is_expected.to eq 'placeholder_600.png' } + describe "uses planting's own photo" do before { planting.photos << planting_photo } - it { is_expected.to eq planting_photo.thumbnail_url } + it { is_expected.to eq planting_photo.fullsize_url } end end @@ -70,10 +71,11 @@ describe PhotosHelper do subject { harvest_image_path(harvest) } it { is_expected.to eq 'placeholder_600.png' } + describe "uses harvest's own photo" do before { harvest.photos << harvest_photo } - it { is_expected.to eq harvest_photo.thumbnail_url } + it { is_expected.to eq harvest_photo.fullsize_url } end end @@ -85,7 +87,7 @@ describe PhotosHelper do describe "uses seed's own photo" do before { seed.photos << seed_photo } - it { is_expected.to eq seed_photo.thumbnail_url } + it { is_expected.to eq seed_photo.fullsize_url } end end end diff --git a/spec/models/crop_companion_spec.rb b/spec/models/crop_companion_spec.rb new file mode 100644 index 000000000..7b6223db5 --- /dev/null +++ b/spec/models/crop_companion_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe CropCompanion, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index c5dbe582b..d780aedb2 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -96,6 +96,7 @@ RSpec.configure do |config| # controller specs require this to work with Devise # see https://github.com/plataformatec/devise/wiki/How-To%3a-Controllers-and-Views-tests-with-Rails-3-%28and-rspec%29 config.include Devise::Test::ControllerHelpers, type: :controller + config.include Devise::Test::IntegrationHelpers, type: :request config.extend ControllerMacros, type: :controller # Allow just create(:factory) instead of needing to specify FactoryBot.create(:factory) diff --git a/spec/requests/conversations_spec.rb b/spec/requests/conversations_spec.rb new file mode 100644 index 000000000..1ed996685 --- /dev/null +++ b/spec/requests/conversations_spec.rb @@ -0,0 +1,62 @@ +require 'rails_helper' + +describe 'Converstions' do + describe 'DELETE destroy_multiple' do + let!(:member) { create(:admin_member) } + + before do + sign_in member + end + + it 'redirects to the conversations inbox' do + delete '/conversations/destroy_multiple', params: { conversation_ids: [] } + expect(response).to redirect_to '/conversations' + follow_redirect! + expect(response).to render_template(:index) + expect(response).to have_http_status(200) + end + + it 'allows users to trash multiple inbox conversations' do + first_conversation = create(:notification, recipient: member) + second_conversation = create(:notification, recipient: member) + conversations_to_trash = [first_conversation.id, second_conversation.id] + + # we dont actually destroy the messages, we move them to the trash folder + expect do + delete '/conversations/destroy_multiple', params: { conversation_ids: conversations_to_trash, box: 'inbox' } + end.to_not change(Mailboxer::Conversation, :count) + + expect(member.mailbox.inbox.count).to eq 0 + expect(member.mailbox.trash.count).to eq 2 + end + + it 'only deletes conversations for the current user' do + second_member = create(:admin_member) + first_conversation = create(:notification, sender: member, recipient: second_member) + second_conversation = create(:notification, sender: member, recipient: second_member) + conversations_to_trash = [first_conversation.id, second_conversation.id] + + # we dont actually destroy the messages, we move them to the trash folder + expect do + delete '/conversations/destroy_multiple', params: { conversation_ids: conversations_to_trash } + end.to_not change(Mailboxer::Conversation, :count) + + expect(second_member.mailbox.inbox.count).to eq 2 + expect(second_member.mailbox.trash.count).to eq 0 + end + + it 'allows users to trash multiple sent conversations' do + first_conversation = create(:notification, sender: member) + second_conversation = create(:notification, sender: member) + conversations_to_trash = [first_conversation.id, second_conversation.id] + + # we dont actually destroy the messages, we move them to the trash folder + expect do + delete '/conversations/destroy_multiple', params: { conversation_ids: conversations_to_trash, box: 'sent' } + end.to_not change(Mailboxer::Conversation, :count) + + expect(member.mailbox.sentbox.count).to eq 0 + expect(member.mailbox.trash.count).to eq 2 + end + end +end diff --git a/spec/routing/roles_routing_spec.rb b/spec/routing/roles_routing_spec.rb index 7b6469efb..c601e27e3 100644 --- a/spec/routing/roles_routing_spec.rb +++ b/spec/routing/roles_routing_spec.rb @@ -1,33 +1,33 @@ require "rails_helper" -describe RolesController do +describe Admin::RolesController do describe "routing" do it "routes to #index" do - get("/roles").should route_to("roles#index") + expect(get("/admin/roles")).to route_to("admin/roles#index") end it "routes to #new" do - get("/roles/new").should route_to("roles#new") + expect(get("/admin/roles/new")).to route_to("admin/roles#new") end it "routes to #show" do - get("/roles/1").should route_to("roles#show", id: "1") + expect(get("/admin/roles/1")).to route_to("admin/roles#show", id: "1") end it "routes to #edit" do - get("/roles/1/edit").should route_to("roles#edit", id: "1") + expect(get("/admin/roles/1/edit")).to route_to("admin/roles#edit", id: "1") end it "routes to #create" do - post("/roles").should route_to("roles#create") + expect(post("/admin/roles")).to route_to("admin/roles#create") end it "routes to #update" do - put("/roles/1").should route_to("roles#update", id: "1") + expect(put("/admin/roles/1")).to route_to("admin/roles#update", id: "1") end it "routes to #destroy" do - delete("/roles/1").should route_to("roles#destroy", id: "1") + expect(delete("/admin/roles/1")).to route_to("admin/roles#destroy", id: "1") end end end diff --git a/spec/views/admin/index_spec.rb b/spec/views/admin/index_spec.rb index 7e8c27fdc..7379519cc 100644 --- a/spec/views/admin/index_spec.rb +++ b/spec/views/admin/index_spec.rb @@ -9,11 +9,11 @@ describe 'admin/index.html.haml', type: "view" do end it "includes links to manage various things" do - assert_select "a", href: roles_path + assert_select "a", href: admin_roles_path assert_select "a", href: forums_path end it "has a link to newsletter subscribers" do - rendered.should have_content "Newsletter subscribers" + expect(rendered).to have_content "Newsletter subscribers" end end diff --git a/spec/views/admin/newsletter_spec.rb b/spec/views/admin/newsletter_spec.rb index 4807aaa8e..fbae64def 100644 --- a/spec/views/admin/newsletter_spec.rb +++ b/spec/views/admin/newsletter_spec.rb @@ -11,6 +11,6 @@ describe 'admin/newsletter.html.haml', type: "view" do end it "lists newsletter subscribers by email" do - rendered.should have_content @subscriber.email + expect(rendered).to have_content @subscriber.email end end diff --git a/spec/views/roles/edit.html.haml_spec.rb b/spec/views/admin/roles/edit.html.haml_spec.rb similarity index 65% rename from spec/views/roles/edit.html.haml_spec.rb rename to spec/views/admin/roles/edit.html.haml_spec.rb index f546c7c21..62dfaf3b3 100644 --- a/spec/views/roles/edit.html.haml_spec.rb +++ b/spec/views/admin/roles/edit.html.haml_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe "roles/edit" do +describe "admin/roles/edit" do before do @role = assign(:role, stub_model(Role, name: "MyString", @@ -10,8 +10,7 @@ describe "roles/edit" do it "renders the edit role form" do render - # Run the generator again with the --webrat flag if you want to use webrat matchers - assert_select "form", action: roles_path(@role), method: "post" do + assert_select "form", action: admin_roles_path(@role), method: "post" do assert_select "input#role_name", name: "role[name]" assert_select "textarea#role_description", name: "role[description]" end diff --git a/spec/views/roles/index.html.haml_spec.rb b/spec/views/admin/roles/index.html.haml_spec.rb similarity index 94% rename from spec/views/roles/index.html.haml_spec.rb rename to spec/views/admin/roles/index.html.haml_spec.rb index a666a4ac8..b94d20dba 100644 --- a/spec/views/roles/index.html.haml_spec.rb +++ b/spec/views/admin/roles/index.html.haml_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe "roles/index" do +describe "admin/roles/index" do before do controller.stub(:current_user) { nil } assign(:roles, [ diff --git a/spec/views/roles/new.html.haml_spec.rb b/spec/views/admin/roles/new.html.haml_spec.rb similarity index 81% rename from spec/views/roles/new.html.haml_spec.rb rename to spec/views/admin/roles/new.html.haml_spec.rb index d04afacef..6c27927f2 100644 --- a/spec/views/roles/new.html.haml_spec.rb +++ b/spec/views/admin/roles/new.html.haml_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe "roles/new" do +describe "admin/roles/new" do before do assign(:role, stub_model(Role, name: "MyString", @@ -11,7 +11,7 @@ describe "roles/new" do render # Run the generator again with the --webrat flag if you want to use webrat matchers - assert_select "form", action: roles_path, method: "post" do + assert_select "form", action: admin_roles_path, method: "post" do assert_select "input#role_name", name: "role[name]" assert_select "textarea#role_description", name: "role[description]" end diff --git a/spec/views/crops/_planting_advice.html.haml_spec.rb b/spec/views/crops/_planting_advice.html.haml_spec.rb deleted file mode 100644 index 59e635b45..000000000 --- a/spec/views/crops/_planting_advice.html.haml_spec.rb +++ /dev/null @@ -1,67 +0,0 @@ -require 'rails_helper' - -describe "crops/_planting_advice" do - subject { rendered } - - let(:planting) { FactoryBot.create(:planting) } - - shared_context "render planting_advice" do - before { render 'crops/planting_advice', crop: planting.crop } - end - - describe "sunniness" do - context "with no sunniness set" do - include_examples "render planting_advice" - it "doesn't show sunniness" do - expect(subject).to have_content "Plant in: not known." - end - end - - context "with sunniness frequencies" do - before { FactoryBot.create(:sunny_planting, crop: planting.crop) } - - include_examples "render planting_advice" - it { is_expected.to have_content "Plant in:" } - it { is_expected.to have_content "sun (1)" } - end - - context "with multiple sunniness frequencies" do - before do - FactoryBot.create_list(:sunny_planting, 2, crop: planting.crop) - FactoryBot.create(:shady_planting, crop: planting.crop) - end - - include_examples "render planting_advice" - it { is_expected.to have_content "Plant in:" } - it { is_expected.to have_content "sun (2), shade (1)" } - end - end - - describe "planted from" do - context "when none are set" do - include_examples "render planting_advice" - it "doesn't show planted_from " do - expect(subject).to have_content "Plant from: not known." - end - end - - context "with planted_from frequencies" do - before { FactoryBot.create(:seed_planting, crop: planting.crop) } - - include_examples "render planting_advice" - it { is_expected.to have_content "Plant from:" } - it { is_expected.to have_content "seed (1)" } - end - - context "with multiple planted_from frequencies" do - before do - FactoryBot.create_list(:seed_planting, 2, crop: planting.crop) - FactoryBot.create(:cutting_planting, crop: planting.crop) - end - - include_examples "render planting_advice" - it { is_expected.to have_content "Plant from:" } - it { is_expected.to have_content "seed (2), cutting (1)" } - end - end -end diff --git a/spec/views/gardens/show.html.haml_spec.rb b/spec/views/gardens/show.html.haml_spec.rb index 29841111f..7535ce8de 100644 --- a/spec/views/gardens/show.html.haml_spec.rb +++ b/spec/views/gardens/show.html.haml_spec.rb @@ -6,9 +6,11 @@ describe "gardens/show" do controller.stub(:current_user) { @owner } @garden = FactoryBot.create(:garden, owner: @owner) @planting = FactoryBot.create(:planting, garden: @garden, owner: @garden.owner) + @suggested_companions = FactoryBot.create_list :crop, 4 assign(:garden, @garden) assign(:current_plantings, [@planting]) assign(:finished_plantings, []) + assign(:suggested_companions, @suggested_companions) render end diff --git a/spec/views/harvests/index.html.haml_spec.rb b/spec/views/harvests/index.html.haml_spec.rb index 461749f0b..294256ade 100644 --- a/spec/views/harvests/index.html.haml_spec.rb +++ b/spec/views/harvests/index.html.haml_spec.rb @@ -27,7 +27,7 @@ describe "harvests/index" do it "provides data links" do render - rendered.should have_content "The data on this page is available in the following formats:" + expect(rendered).to have_content "The data on this page is available in the following formats:" assert_select "a", href: harvests_path(format: 'csv') assert_select "a", href: harvests_path(format: 'json') end diff --git a/spec/views/harvests/show.html.haml_spec.rb b/spec/views/harvests/show.html.haml_spec.rb index f3c17f2b5..4a8053924 100644 --- a/spec/views/harvests/show.html.haml_spec.rb +++ b/spec/views/harvests/show.html.haml_spec.rb @@ -14,7 +14,7 @@ describe "harvests/show" do describe "renders attributes" do it { is_expected.to have_content harvest.crop.name } - it { is_expected.to have_content harvest.harvested_at.to_s } + it { is_expected.to have_content I18n.l(harvest.harvested_at) } it { is_expected.to have_content harvest.plant_part.to_s } end end diff --git a/spec/views/photos/index.html.haml_spec.rb b/spec/views/photos/index.html.haml_spec.rb index f3eae4739..ef058e822 100644 --- a/spec/views/photos/index.html.haml_spec.rb +++ b/spec/views/photos/index.html.haml_spec.rb @@ -17,6 +17,6 @@ describe "photos/index" do it "renders a gallery of photos" do render assert_select ".photo-card", count: 2 - assert_select "img", count: 2 + assert_select "img.img-card", count: 2 end end diff --git a/spec/views/photos/show.html.haml_spec.rb b/spec/views/photos/show.html.haml_spec.rb index a4a275751..f0f1235ef 100644 --- a/spec/views/photos/show.html.haml_spec.rb +++ b/spec/views/photos/show.html.haml_spec.rb @@ -21,22 +21,25 @@ describe "photos/show" do end it "links to the owner's profile" do - assert_select "a", href: @photo.owner + expect(rendered).to have_link(href: member_path(@photo.owner)) end it "shows a link to the original image" do - assert_select "a", href: @photo.link_url, text: "View on Flickr" + expect(rendered).to have_link 'View on Flickr', href: @photo.link_url end it "links to harvest" do assert_select "a", href: harvest_path(harvest) end + it "links to planting" do assert_select "a", href: planting_path(planting) end + it "links to garden" do assert_select "a", href: garden_path(garden) end + it "links to seeds" do assert_select "a", href: seed_path(seed) end diff --git a/spec/views/roles/show.html.haml_spec.rb b/spec/views/roles/show.html.haml_spec.rb deleted file mode 100644 index 1f41bb23c..000000000 --- a/spec/views/roles/show.html.haml_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'rails_helper' - -describe "roles/show" do - before do - @role = assign(:role, stub_model(Role, - name: "Name", - description: "MyText")) - end - - it "renders attributes in " do - render - # Run the generator again with the --webrat flag if you want to use webrat matchers - rendered.should match(/Name/) - rendered.should match(/MyText/) - end -end diff --git a/spec/views/seeds/show.html.haml_spec.rb b/spec/views/seeds/show.html.haml_spec.rb index 1c2e88442..34525fbb6 100644 --- a/spec/views/seeds/show.html.haml_spec.rb +++ b/spec/views/seeds/show.html.haml_spec.rb @@ -29,7 +29,7 @@ describe "seeds/show" do end it "shows tradable attributes" do - expect(rendered).to have_content "Will trade: locally" + expect(rendered).to have_content "Will trade locally" end it "shows button to send message" do
" do - render - # Run the generator again with the --webrat flag if you want to use webrat matchers - rendered.should match(/Name/) - rendered.should match(/MyText/) - end -end diff --git a/spec/views/seeds/show.html.haml_spec.rb b/spec/views/seeds/show.html.haml_spec.rb index 1c2e88442..34525fbb6 100644 --- a/spec/views/seeds/show.html.haml_spec.rb +++ b/spec/views/seeds/show.html.haml_spec.rb @@ -29,7 +29,7 @@ describe "seeds/show" do end it "shows tradable attributes" do - expect(rendered).to have_content "Will trade: locally" + expect(rendered).to have_content "Will trade locally" end it "shows button to send message" do