From 79d8e6d253fb6121689d0031b118ca554287f82f Mon Sep 17 00:00:00 2001 From: loqimean Date: Mon, 3 Jun 2024 12:52:22 +0300 Subject: [PATCH 01/39] chore: use correct ruby version --- Capfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Capfile b/Capfile index 199cbd861..154f58192 100644 --- a/Capfile +++ b/Capfile @@ -15,7 +15,7 @@ require "capistrano/rbenv" require "dotenv" set :rbenv_type, :user -set :rbenv_ruby, "2.7.2" +set :rbenv_ruby, "3.2.2" Dotenv.load From b633c968bfd773a58af6d17f7f28aaf835675b62 Mon Sep 17 00:00:00 2001 From: loqimean Date: Mon, 3 Jun 2024 13:56:20 +0300 Subject: [PATCH 02/39] chore: add missing checking for the favicon --- app/views/account/site_settings/_browser_tab.html.slim | 2 +- app/views/layouts/account.html.erb | 4 ++-- app/views/layouts/application.html.erb | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/account/site_settings/_browser_tab.html.slim b/app/views/account/site_settings/_browser_tab.html.slim index c59888c57..ed5239f86 100644 --- a/app/views/account/site_settings/_browser_tab.html.slim +++ b/app/views/account/site_settings/_browser_tab.html.slim @@ -1,5 +1,5 @@ .tab-browser .tab-content - = image_tag(SiteSetting.current.favicon, class: 'tab-icon', alt: 'Tab Browser Image', data: { handle_browser_tab_target: "icon" }) + = image_tag(SiteSetting.current.favicon, class: 'tab-icon', alt: 'Tab Browser Image', data: { handle_browser_tab_target: "icon" }) if SiteSetting.current.favicon.attached? span.tab-text data-handle-browser-tab-target="title" = SiteSetting.current.title diff --git a/app/views/layouts/account.html.erb b/app/views/layouts/account.html.erb index 468db7d8c..fa7c19bbb 100644 --- a/app/views/layouts/account.html.erb +++ b/app/views/layouts/account.html.erb @@ -2,7 +2,7 @@ - <%= favicon_link_tag(url_for(SiteSetting.current.favicon)) %> + <%= favicon_link_tag(url_for(SiteSetting.current.favicon)) if SiteSetting.current.favicon.attached? %> <%= display_meta_tags( site: SiteSetting.current.title, reverse: true @@ -14,7 +14,7 @@ <%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %> <%= javascript_importmap_tags %> - + <%= render 'layouts/flash_messages' %> <%= render 'layouts/account_navbar' %>
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 33d027d80..b49c69fe7 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -4,7 +4,7 @@ <%= csrf_meta_tags %> <%= csp_meta_tag %> - <%= favicon_link_tag(url_for(SiteSetting.current.favicon)) %> + <%= favicon_link_tag(url_for(SiteSetting.current.favicon)) if SiteSetting.current.favicon.attached? %> <%= display_meta_tags( site: SiteSetting.current.title, viewport: 'width=device-width, initial-scale=1.0', From f2c1e0198ef4d39a1d6523042421f9df3fcf38c4 Mon Sep 17 00:00:00 2001 From: alexy78 Date: Fri, 25 Oct 2024 14:17:42 +0300 Subject: [PATCH 03/39] 920 Added seeds for categories --- db/seeds.rb | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/db/seeds.rb b/db/seeds.rb index 99495cd83..e3d78a3de 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -30,3 +30,47 @@ end FactoryBot.create(:product, :diaper) unless Product.exists?(title: "diaper") + +# Categories +def create_category_with_periods(en_name, uk_name, periods) + unless Category.exists?(en_name: en_name) + category = Category.create!(en_name: en_name, uk_name: uk_name) + + periods.each do |period_data| + DiapersPeriod.create!(period_data.merge(category: category)) + end + end +end + +# Budgetary category periods +create_category_with_periods("Budgetary", "Бюджетна", [ + { period_start: 1, period_end: 3, usage_amount: 10, price: 5.96 }, + { period_start: 4, period_end: 6, usage_amount: 8, price: 6.35 }, + { period_start: 7, period_end: 9, usage_amount: 6, price: 6.35 }, + { period_start: 10, period_end: 12, usage_amount: 6, price: 6.45 }, + { period_start: 13, period_end: 18, usage_amount: 4, price: 6.45 }, + { period_start: 19, period_end: 24, usage_amount: 4, price: 7.6 }, + { period_start: 25, period_end: 30, usage_amount: 2, price: 7.6 } +]) + +# Medium category periods +create_category_with_periods("Medium", "Середня", [ + { period_start: 1, period_end: 3, usage_amount: 10, price: 8.03 }, + { period_start: 4, period_end: 6, usage_amount: 8, price: 8.33 }, + { period_start: 7, period_end: 9, usage_amount: 6, price: 8.65 }, + { period_start: 10, period_end: 12, usage_amount: 6, price: 10.07 }, + { period_start: 13, period_end: 18, usage_amount: 4, price: 10.07 }, + { period_start: 19, period_end: 24, usage_amount: 4, price: 11.13 }, + { period_start: 25, period_end: 30, usage_amount: 2, price: 11.13 } +]) + +# Premium category periods +create_category_with_periods("Premium", "Преміум", [ + { period_start: 1, period_end: 3, usage_amount: 10, price: 11.41 }, + { period_start: 4, period_end: 6, usage_amount: 8, price: 12.59 }, + { period_start: 7, period_end: 9, usage_amount: 6, price: 14.18 }, + { period_start: 10, period_end: 12, usage_amount: 6, price: 18.52 }, + { period_start: 13, period_end: 18, usage_amount: 4, price: 18.52 }, + { period_start: 19, period_end: 24, usage_amount: 4, price: 18.62 }, + { period_start: 25, period_end: 30, usage_amount: 2, price: 18.62 } +]) From a2e8d451852f782e330047138cc743f0e7059b6f Mon Sep 17 00:00:00 2001 From: Nataliia Makarenko <73568587+NVMakarenko@users.noreply.github.com> Date: Mon, 28 Oct 2024 12:00:00 +0200 Subject: [PATCH 04/39] 921 update readme file (#925) * Update README.md add description for installation on Windows. * Update README.md add description for Linux installation * Update README.md update Linux description * Update README.md add description for macOS installation. --- README.md | 480 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 345 insertions(+), 135 deletions(-) diff --git a/README.md b/README.md index 6c5aa996f..aa313567f 100644 --- a/README.md +++ b/README.md @@ -12,25 +12,17 @@ Zero Waste Lviv is a Public Organization that works on the implementation of was In order to attract attention to financial and ecological consequences of disposable diaper usage it is planned to create a module that will calculate budget spent on diapers and calculations of the future expenses. As visual representation it is planned to show the volume of waste that was made during usage of disposable diapers for one child. - [Deployed Apps and Environments](#deployed-apps-and-environments) +- [Required to install](#required-to-install) - [Installation](#installation) - - [Required to install](#Required-to-install) - - [Clone](#Clone) - - [Local setup](#Setup) - - [How to run local](#How-to-run-local) -- [Usage](#Usage) - - [How to run Rubocop](#How-to-run-Rubocop) - - [Git-hook pre-commit](#Git-hook-pre-commit) +- [Usage](#usage) +- [Contributing](#contributing) ## Deployed Apps and Environments The latest version from the 'develop' branch is automatically deployed to stage environment in Render, [staging link](https://zero-waste-staging.onrender.com/). The latest version from the release branch 'master' is automatically deployed to Production environment, [production link](http://calc.zerowastelviv.org.ua/). -## Installation - -- Start the project locally - -# Required to install +## Required to install - Ruby 3.2.2 - Ruby on Rails 7.1.2 @@ -38,130 +30,348 @@ The latest version from the release branch 'master' is automatically deployed to - Puma as a web server - Yarn - Bootstrap + +## Installation -## Clone - -$ `git clone https://github.com/ita-social-projects/ZeroWaste.git` - -## Local setup - -First of all you need RVM to setup project. For the operating system Windows the optimal solution is to use WSL. - -$ `bin/setup` -or -$ `bundle install` - -Install the following packages: - -`sudo apt install imagemagick` - -`sudo apt install libvips42` - -PostgreSQL - -Install PostgreSQL for your operating system or subsystem. -You can familiarize yourself with PostgreSQL documentation. - -To check if it is installed and running correctly run `sudo systemctl status postgresql` - -In your local machine in cloned project in config folder rename database.yml.sample to database.yml. Make sure that the user and password match the data in this file. Port may be changed. - -For further work, make sure that you have a user 'postgres' with superuser. If is no that one do next: -$ `sudo -u user psql user` -$ `CREATE USER postgres SUPERUSER;` -$ `CREATE DATABASE postgres WITH OWNER postgres;` - -If you're having trouble authenticating, you may need to reset your password. You can read instruction how to do it. - -pg gem - -Under certain circumstances bundle can do not install pg. - -To install manually: -$ `sudo apt-get install libpq-dev` -then -$ `gem install pg` - -Database configure - -For correct operation of the migration, you need to rename the migration file `20220123171144_create_versions.rb` so that it is processed first. - -To create the necessary databases and update them: - -$ `rake db:create` -then -$ `rake db:migrate` - -$ `rake db:reset` can resolve some errors connected with database. - -Redis - -You need Redis for correct work. -Install Redis for your operating system or subsystem. You can familiarize yourself with -Redis documentation. - -Installation for Ubuntu: - -$ `curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg` - -```shell -echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list -``` - -$ `sudo apt-get update` -$ `sudo apt-get install redis` - -To check if it is installed and running correctly run `sudo systemctl status redis` - -npm and Node.js - -Also you need Node.js. -Install npm and Node.js for your operating system or subsystem. You can familiarize yourself with -npm and Node.js documentation - -yarn - -You can read more about yarn there: -yarn documentation. - -Installation: -$ `npm install --global yarn` - -Webpacker - -To prevent an error when starting the server install webpacker. You can read more about webpacker there: -Webpacker documentation. - -Installation: -$ `yarn add @rails/webpacker` -$ `bundle update webpacker` - -Sidekiq - -Simple, efficient background processing for Ruby. You can read more about sidekiq there: -Sidekiq documentation. - -Installation: -$ `bundle add sidekiq` - -## How to run local - -1. Ensure that postgresql and redis are running -2. Run `rails assets:precompile` to precompile assets -3. Run `bin/rails tailwindcss:watch` with `rails server` to watch for changes in tailwind and start server or run `bin/dev` -4. Open http://localhost:3000 to view it in the browser. - -Solutions when an errors occurs: -psql: FATAL: role "postgres" does not exist - -If you have Webpacker::Manifest::MissingEntryError you can try next steps: -$ `rm -rf node_modules` -$ `rails webpacker:install` -$ `yarn install` - -# Usage + To install ZeroWaste, follow these steps: + +
+

Windows

+ + First of all you need RVM to setup project. For the operating system Windows the optimal solution is to use WSL 2. + + **1. Clone the repository:** + + $ `git clone https://github.com/ita-social-projects/ZeroWaste.git` + + **2. Navigate to the project directory:** + + $ `cd project-title` + + **3. Install the following libraries for image pocessing:** + + `sudo apt install imagemagick` + + `sudo apt install libvips42` + + **4. Install all of a project's dependencies:** + + $ `bin/setup` + or + $ `bundle install` + + **5. Install PostgresSQL** + + To check if PostgreSQL is installed and running correctly run `sudo systemctl status postgresql` + + | Status | Next step | + | ------------- | ------------- | + | Not installed | Install PostgreSQL for your operating system or subsystem. You can familiarize yourself with PostgreSQL documentation.| + | Installed but inactive | Start PostgreSQL `sudo systemctl start postgresql` | + | Installed and avtive | Move to the next step. | + + **6. Database configure** + + In your local machine in cloned project in config folder rename database.yml.sample to database.yml. Make sure that the user and password match the data in this file. Port may be changed. + + For further work, make sure that you have a user 'postgres' with proper password. + Create database: + $ `sudo su postgres` + $ `CREATE DATABASE zero_waste_development;` + $ `CREATE DATABASE zero_waste_test;` + + If you're having trouble authenticating, you may need to reset your password. You can read instruction how to do it. + + To update databases run: + + $ `rake db:migrate` + + $ `rake db:reset` can resolve some errors connected with database. + + **7. Install Redis** + + You need Redis for correct work. + Install Redis for your operating system or subsystem. You can familiarize yourself with + Redis documentation. + + ``` + curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg + + echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list + + sudo apt-get update + sudo apt-get install redis + ``` + + Lastly, start the Redis server like so: + + $ `sudo service redis-server start` + + To check if it is installed and running correctly run `sudo systemctl status redis-server` + + **8. Install Yarn** + + You can read more about yarn there: + yarn documentation. + + For Windows doqnload the yarn installer. + + This will give you a .msi file that when run will walk you through installing Yarn on Windows. + + If you use the installer you will first need to install Node.js. + + **9. Install Sidekiq** + Simple, efficient background processing for Ruby. You can read more about sidekiq there: + Sidekiq documentation. + Installation: + $ `bundle add sidekiq` + +**First run** + 1. Ensure that postgresql and redis are running + 2. Run `rails assets:precompile` to precompile assets + 3. Run `bin/rails tailwindcss:watch` with `rails server` to watch for changes in tailwind and start server or run `bin/dev` + +**Access the application** + Open http://localhost:3000 to view it in the browser. + + Solutions when an errors occurs: + psql: FATAL: role "postgres" does not exist +
+ +
+

Linux

+ First, ensure RVM is installed for Ruby management. You can install RVM by following the official RVM installation guide. Make sure to follow any instructions for setting up your shell. + + **1. Clone the repository:** + + $ `git clone https://github.com/ita-social-projects/ZeroWaste.git` + + **2. Navigate to the project directory:** + + $ `cd project-title` + + **3. Install the following libraries for image pocessing:** + + `sudo apt install imagemagick` + + `sudo apt install libvips42` + + **4. Install all of a project's dependencies:** + + $ `bin/setup` + or + $ `bundle install` + + **5. Install PostgresSQL** + + Ensure PostgreSQL is installed and active: + + ``` + sudo apt update + sudo apt install postgresql postgresql-contrib + ``` + + To check if PostgreSQL is running: `sudo systemctl status postgresql` + + | Status | Next step | + | ------------- | ------------- | + | Not installed | Install PostgreSQL for your operating system or subsystem. You can familiarize yourself with PostgreSQL documentation.| + | Installed but inactive | Start PostgreSQL `sudo systemctl start postgresql` | + | Installed and avtive | Move to the next step. | + + **6. Database configuration** + + In the config folder, rename database.yml.sample to database.yml. Update it with your PostgreSQL username and password, and adjust the port if necessary. + + To set up the database: + + ``` + sudo -u postgres psql -c "CREATE DATABASE zero_waste_development;" + sudo -u postgres psql -c "CREATE DATABASE zero_waste_test;" + ``` + + If you're having trouble authenticating, you may need to reset your password. You can read instruction how to do it. + + Run Database migrations: + + $ `rake db:migrate` + + If issues arise, reset the database: + + $ `rake db:reset` + + **7. Install Redis** + + Install Redis for background job processing: + Install Redis for your operating system or subsystem. You can familiarize yourself with + Redis documentation. + + ``` + sudo apt update + sudo apt install redis + ``` + + Start the Redis server: + + $ `sudo service redis-server start` + + Verify Redis is active `sudo systemctl status redis-server` + + **8. Install Yarn** + + You can read more about yarn there: + yarn documentation. + + **9. Install Sidekiq** + + Sidekiq handles background processing in Ruby. Install it with: + Sidekiq documentation. + + Installation: + + $ `bundle add sidekiq` + + **First run** + + 1. Confirm PostgreSQL and Redis are running. + 2. Run `rails assets:precompile` to precompile assets + 3. Run `bin/rails tailwindcss:watch` with `rails server` to watch for changes in tailwind and start server or run `bin/dev` + + **Access the application** + Open http://localhost:3000 to view ZeroWaste in the browser. + + Solutions when an errors occurs: + psql: FATAL: role "postgres" does not exist +
+ +
+

macOS

+ + First, ensure RVM is installed for Ruby management. You can install RVM by following the official RVM installation guide. Make sure to follow any instructions for setting up your shell. + + **1. Clone the repository:** + + $ `git clone https://github.com/ita-social-projects/ZeroWaste.git` + + **2. Navigate to the project directory:** + + $ `cd project-title` + + **3. Install the following libraries for image pocessing:** + + `brew install imagemagick` + + `brew install libvips42` + + **4. Install all of a project's dependencies:** + + $ `bin/setup` + or + $ `bundle install` + + **5. Install PostgresSQL** + + Ensure PostgreSQL is installed and active: + + ``` + brew install postgresql + ``` + + After installation, start PostgreSQL: `brew services start postgresql` + + **6. Database configuration** + + In the config folder, rename database.yml.sample to database.yml. + + Update it with your PostgreSQL username and password, and adjust the port if necessary. + + ``` + psql -U postgres + CREATE DATABASE zero_waste_development; + CREATE DATABASE zero_waste_test; + \q + ``` + + If you're having trouble authenticating, you may need to reset your password. You can read instruction how to do it. + + Run Database migrations: + + $ `rake db:migrate` + + If issues arise, reset the database: + + $ `rake db:reset` + + **7. Install Redis** + + Install Redis for background tasks processing: + Install Redis for your operating system or subsystem. You can familiarize yourself with + Redis documentation. + + ``` + brew install redis + ``` + + Start the Redis service: + + $ `brew services start redis` + + **8. Install Yarn** + + You can read more about yarn there: + yarn documentation. + + Install Yarn using Homebrew. You may need Node.js as well if it’s not installed. + + `brew install yarn` + + **9. Install Sidekiq** + + Sidekiq handles background processing in Ruby. Install it with: + Sidekiq documentation. + + Installation: + + $ `bundle add sidekiq` + +**First run** + + 1. Confirm PostgreSQL and Redis are running. + 2. Run `rails assets:precompile` to precompile assets + 3. Run `bin/rails tailwindcss:watch` with `rails server` to watch for changes in tailwind and start server or run `bin/dev` + +**Access the application** + Open http://localhost:3000 to view ZeroWaste in the browser. + + Solutions when an errors occurs: + psql: FATAL: role "postgres" does not exist +
+ + +## Usage + +To use ZeroWaste, follow these steps: +1. Run `bin/rails tailwindcss:watch` with `rails server` to watch for changes in tailwind +2. Start server `rails s` or run `bin/dev` +3. Open http://localhost:3000 to view it in the browser. + +## Contributing + +If you'd like to contribute to ZeroWaste, here are some guidelines: + +1. Create a new branch for your changes. +2. Make your changes. +3. Write tests to cover your changes. +4. Run the tests to ensure they pass. +5. Commit your changes. +6. Push your changes to your forked repository. +7. Submit a pull request. + +**Before commitying check your code style using [Rubocop](#rubocop)** + +**[Git-hoor pre-commit](#git-hook-pre-commit) will run automatically before commit** -# How to run Rubocop +### Rubocop Running rubocop with no arguments will check all Ruby source files in the current folder: @@ -175,7 +385,7 @@ For more details check the available command-line options: $ `rubocop -h` -# Git-hook pre-commit +### Git-hook pre-commit Before using `git-hook-pre-commit` you need to install `sudo apt-get install cmake` From fb2ea4e6b8a382d537fff628b957a4725c39e6e3 Mon Sep 17 00:00:00 2001 From: Ihor Simashko Date: Mon, 28 Oct 2024 23:39:29 +0200 Subject: [PATCH 05/39] added custom 404, 422, 500 error pages --- app/controllers/errors_controller.rb | 13 +++++++++++++ app/views/errors/internal_server.html.erb | 4 ++++ app/views/errors/not_found.html.erb | 4 ++++ app/views/errors/unprocessable.html.erb | 4 ++++ config/application.rb | 2 ++ config/locales/en/en.yml | 9 +++++++++ config/locales/uk/uk.yml | 9 +++++++++ config/routes.rb | 4 ++++ 8 files changed, 49 insertions(+) create mode 100644 app/controllers/errors_controller.rb create mode 100644 app/views/errors/internal_server.html.erb create mode 100644 app/views/errors/not_found.html.erb create mode 100644 app/views/errors/unprocessable.html.erb diff --git a/app/controllers/errors_controller.rb b/app/controllers/errors_controller.rb new file mode 100644 index 000000000..8e31ca126 --- /dev/null +++ b/app/controllers/errors_controller.rb @@ -0,0 +1,13 @@ +class ErrorsController < ApplicationController + def not_found + render status: :not_found + end + + def unprocessable + render status: :unprocessable_entity + end + + def internal_server + render status: :internal_server_error + end +end diff --git a/app/views/errors/internal_server.html.erb b/app/views/errors/internal_server.html.erb new file mode 100644 index 000000000..9e8809576 --- /dev/null +++ b/app/views/errors/internal_server.html.erb @@ -0,0 +1,4 @@ +
+

<%= t('500_page.title') %> +

<%= t('500_page.description') %>

+
diff --git a/app/views/errors/not_found.html.erb b/app/views/errors/not_found.html.erb new file mode 100644 index 000000000..9a2ddf34f --- /dev/null +++ b/app/views/errors/not_found.html.erb @@ -0,0 +1,4 @@ +
+

<%= t('404_page.title') %> +

<%= t('404_page.description') %>

+
diff --git a/app/views/errors/unprocessable.html.erb b/app/views/errors/unprocessable.html.erb new file mode 100644 index 000000000..395fd09bf --- /dev/null +++ b/app/views/errors/unprocessable.html.erb @@ -0,0 +1,4 @@ +
+

<%= t('422_page.title') %> +

<%= t('422_page.description') %>

+
diff --git a/config/application.rb b/config/application.rb index effb599d4..a5422c19c 100644 --- a/config/application.rb +++ b/config/application.rb @@ -28,5 +28,7 @@ class Application < Rails::Application config.i18n.load_path += Dir[Rails.root.join("config", "locales", "**", "*.{rb,yml}")] config.assets.css_compressor = nil + + config.exceptions_app = self.routes end end diff --git a/config/locales/en/en.yml b/config/locales/en/en.yml index 1828133a0..edcdb5f08 100644 --- a/config/locales/en/en.yml +++ b/config/locales/en/en.yml @@ -178,6 +178,15 @@ en: calculator-diapers: "Calculator diapers" calculators: "Calculators" message: "Write us a message" + 404_page: + title: "Oops! Page Not Found" + description: "The page you're looking for doesn't exist. It might have been removed, or the link you followed could be broken. Please check the URL or return to the homepage." + 422_page: + title: "Unprocessable Request" + description: "Something went wrong with the data sent. Please review your submission and try again, or return to the previous page." + 500_page: + title: "Internal Server Error" + description: "We encountered an unexpected issue on our end. Please try again later, or contact support if the problem persists." about_us: title: "About Us" description: diff --git a/config/locales/uk/uk.yml b/config/locales/uk/uk.yml index 57ecc3936..f810c2ede 100644 --- a/config/locales/uk/uk.yml +++ b/config/locales/uk/uk.yml @@ -158,6 +158,15 @@ uk: calculator-diapers: "Калькулятор підгузків" calculators: "Калькулятори" message: "Напишіть нам повідомлення" + 404_page: + title: "Упс! Сторінку не знайдено" + description: "Сторінка, яку ви шукаєте, не існує. Можливо, її було видалено або посилання некоректне. Перевірте URL або поверніться на головну сторінку." + 422_page: + title: "Некоректний запит" + description: "Щось пішло не так з надісланими даними. Перевірте введені дані та спробуйте ще раз або поверніться на попередню сторінку." + 500_page: + title: "Внутрішня помилка сервера" + description: "Ми зіткнулися з неочікуваною проблемою. Будь ласка, спробуйте пізніше або зверніться в службу підтримки, якщо проблема не зникне." about_us: title: "Про нас" description: diff --git a/config/routes.rb b/config/routes.rb index 7de991fc5..cc0d5f258 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -86,4 +86,8 @@ end end end + + get "/404", to: "errors#not_found" + get "/422", to: "errors#unprocessable" + get "/500", to: "errors#internal_server" end From 8ab904310f6459e2b533c3fff6bb37327a826858 Mon Sep 17 00:00:00 2001 From: Ihor Simashko Date: Tue, 29 Oct 2024 00:13:33 +0200 Subject: [PATCH 06/39] added RecordNotFound rescue + changed error page text color --- app/controllers/account/users_controller.rb | 6 ------ app/controllers/application_controller.rb | 6 ++++++ app/views/errors/internal_server.html.erb | 4 ++-- app/views/errors/not_found.html.erb | 4 ++-- app/views/errors/unprocessable.html.erb | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/controllers/account/users_controller.rb b/app/controllers/account/users_controller.rb index e36afe64d..22f111b4b 100644 --- a/app/controllers/account/users_controller.rb +++ b/app/controllers/account/users_controller.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class Account::UsersController < Account::BaseController - rescue_from ActiveRecord::RecordNotFound, with: :render404 - layout "account" before_action :set_paper_trail_whodunnit @@ -78,8 +76,4 @@ def collection def resource User.find(params[:id]) end - - def render404 - render file: Rails.public_path.join("404.html"), layout: false, status: :not_found - end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 11c87f033..179e91463 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -7,6 +7,8 @@ class ApplicationController < ActionController::Base before_action :store_user_location!, if: :storable_location? before_action :configure_permitted_parameters, if: :devise_controller? + rescue_from ActiveRecord::RecordNotFound, with: :render_404 + def redirection redirect_to root_url end @@ -46,6 +48,10 @@ def set_i18n_locale_from_params end end + def render_404 + render "errors/not_found", status: :not_found + end + protected def configure_permitted_parameters diff --git a/app/views/errors/internal_server.html.erb b/app/views/errors/internal_server.html.erb index 9e8809576..38c5f22b7 100644 --- a/app/views/errors/internal_server.html.erb +++ b/app/views/errors/internal_server.html.erb @@ -1,4 +1,4 @@
-

<%= t('500_page.title') %> -

<%= t('500_page.description') %>

+

<%= t('500_page.title') %> +

<%= t('500_page.description') %>

diff --git a/app/views/errors/not_found.html.erb b/app/views/errors/not_found.html.erb index 9a2ddf34f..739c59d02 100644 --- a/app/views/errors/not_found.html.erb +++ b/app/views/errors/not_found.html.erb @@ -1,4 +1,4 @@
-

<%= t('404_page.title') %> -

<%= t('404_page.description') %>

+

<%= t('404_page.title') %> +

<%= t('404_page.description') %>

diff --git a/app/views/errors/unprocessable.html.erb b/app/views/errors/unprocessable.html.erb index 395fd09bf..9baff5afb 100644 --- a/app/views/errors/unprocessable.html.erb +++ b/app/views/errors/unprocessable.html.erb @@ -1,4 +1,4 @@
-

<%= t('422_page.title') %> -

<%= t('422_page.description') %>

+

<%= t('422_page.title') %> +

<%= t('422_page.description') %>

From 63f954dff45a61f0ba7a063303c646a2cc852bfc Mon Sep 17 00:00:00 2001 From: Ihor Simashko Date: Tue, 29 Oct 2024 00:48:48 +0200 Subject: [PATCH 07/39] added specs --- config/routes.rb | 6 +++--- spec/requests/errors_spec.rb | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 spec/requests/errors_spec.rb diff --git a/config/routes.rb b/config/routes.rb index cc0d5f258..cd3338bdb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -87,7 +87,7 @@ end end - get "/404", to: "errors#not_found" - get "/422", to: "errors#unprocessable" - get "/500", to: "errors#internal_server" + get "/404", to: "errors#not_found", as: :not_found_error + get "/422", to: "errors#unprocessable", as: :unprocessable_error + get "/500", to: "errors#internal_server", as: :internal_server_error end diff --git a/spec/requests/errors_spec.rb b/spec/requests/errors_spec.rb new file mode 100644 index 000000000..6bff44220 --- /dev/null +++ b/spec/requests/errors_spec.rb @@ -0,0 +1,27 @@ +require "rails_helper" + +RSpec.describe ErrorsController, type: :request do + describe 'GET #not_found' do + it 'returns a 404 status' do + get not_found_error_path + + expect(response).to have_http_status(:not_found) + end + end + + describe 'GET #unprocessable' do + it 'returns a 422 status' do + get unprocessable_error_path + + expect(response).to have_http_status(:unprocessable_entity) + end + end + + describe 'GET #internal_server' do + it 'returns a 500 status' do + get internal_server_error_path + + expect(response).to have_http_status(:internal_server_error) + end + end +end From 4f71c18b683a7e98c3787d86ca2bda31386af7b4 Mon Sep 17 00:00:00 2001 From: Ihor Simashko Date: Tue, 29 Oct 2024 13:04:16 +0200 Subject: [PATCH 08/39] deleted public error HTMLs + fixes for rubocop --- app/controllers/errors_controller.rb | 26 +++++------ config/application.rb | 2 +- public/404.html | 67 ---------------------------- public/422.html | 67 ---------------------------- public/500.html | 66 --------------------------- spec/requests/errors_spec.rb | 54 +++++++++++----------- 6 files changed, 41 insertions(+), 241 deletions(-) delete mode 100644 public/404.html delete mode 100644 public/422.html delete mode 100644 public/500.html diff --git a/app/controllers/errors_controller.rb b/app/controllers/errors_controller.rb index 8e31ca126..94e43ca8e 100644 --- a/app/controllers/errors_controller.rb +++ b/app/controllers/errors_controller.rb @@ -1,13 +1,13 @@ -class ErrorsController < ApplicationController - def not_found - render status: :not_found - end - - def unprocessable - render status: :unprocessable_entity - end - - def internal_server - render status: :internal_server_error - end -end +class ErrorsController < ApplicationController + def not_found + render status: :not_found + end + + def unprocessable + render status: :unprocessable_entity + end + + def internal_server + render status: :internal_server_error + end +end diff --git a/config/application.rb b/config/application.rb index a5422c19c..912a0af9a 100644 --- a/config/application.rb +++ b/config/application.rb @@ -29,6 +29,6 @@ class Application < Rails::Application config.assets.css_compressor = nil - config.exceptions_app = self.routes + config.exceptions_app = routes end end diff --git a/public/404.html b/public/404.html deleted file mode 100644 index 2be3af26f..000000000 --- a/public/404.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - The page you were looking for doesn't exist (404) - - - - - - -
-
-

The page you were looking for doesn't exist.

-

You may have mistyped the address or the page may have moved.

-
-

If you are the application owner check the logs for more information.

-
- - diff --git a/public/422.html b/public/422.html deleted file mode 100644 index c08eac0d1..000000000 --- a/public/422.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - The change you wanted was rejected (422) - - - - - - -
-
-

The change you wanted was rejected.

-

Maybe you tried to change something you didn't have access to.

-
-

If you are the application owner check the logs for more information.

-
- - diff --git a/public/500.html b/public/500.html deleted file mode 100644 index 78a030af2..000000000 --- a/public/500.html +++ /dev/null @@ -1,66 +0,0 @@ - - - - We're sorry, but something went wrong (500) - - - - - - -
-
-

We're sorry, but something went wrong.

-
-

If you are the application owner check the logs for more information.

-
- - diff --git a/spec/requests/errors_spec.rb b/spec/requests/errors_spec.rb index 6bff44220..fc2b97018 100644 --- a/spec/requests/errors_spec.rb +++ b/spec/requests/errors_spec.rb @@ -1,27 +1,27 @@ -require "rails_helper" - -RSpec.describe ErrorsController, type: :request do - describe 'GET #not_found' do - it 'returns a 404 status' do - get not_found_error_path - - expect(response).to have_http_status(:not_found) - end - end - - describe 'GET #unprocessable' do - it 'returns a 422 status' do - get unprocessable_error_path - - expect(response).to have_http_status(:unprocessable_entity) - end - end - - describe 'GET #internal_server' do - it 'returns a 500 status' do - get internal_server_error_path - - expect(response).to have_http_status(:internal_server_error) - end - end -end +require "rails_helper" + +RSpec.describe ErrorsController, type: :request do + describe "GET #not_found" do + it "returns a 404 status" do + get not_found_error_path + + expect(response).to have_http_status(:not_found) + end + end + + describe "GET #unprocessable" do + it "returns a 422 status" do + get unprocessable_error_path + + expect(response).to have_http_status(:unprocessable_entity) + end + end + + describe "GET #internal_server" do + it "returns a 500 status" do + get internal_server_error_path + + expect(response).to have_http_status(:internal_server_error) + end + end +end From 0efc3a52c1b3219a3bca0bf2a1ebcd0597b20726 Mon Sep 17 00:00:00 2001 From: igorSimash Date: Tue, 29 Oct 2024 13:41:18 +0200 Subject: [PATCH 09/39] fixed user spec --- spec/features/account/users_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/account/users_spec.rb b/spec/features/account/users_spec.rb index 232320d42..8d6a5e46f 100644 --- a/spec/features/account/users_spec.rb +++ b/spec/features/account/users_spec.rb @@ -118,7 +118,7 @@ context "viewing non-existing user" do it "renders the 404 page" do visit account_user_path(id: 1355) - expect(page).to have_content("page you were looking for doesn't exist") + expect(page).to have_content("Oops! Page Not Found") end end end From f471776f2738f41cf74aa95739b365e37c0f0f3f Mon Sep 17 00:00:00 2001 From: alexy78 Date: Fri, 1 Nov 2024 18:00:46 +0200 Subject: [PATCH 10/39] Restrict input to two decimal places in price input fields --- app/javascript/controllers/price_form_controller.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/javascript/controllers/price_form_controller.js b/app/javascript/controllers/price_form_controller.js index 9ecdf8479..8e2262e64 100644 --- a/app/javascript/controllers/price_form_controller.js +++ b/app/javascript/controllers/price_form_controller.js @@ -13,6 +13,7 @@ export default class extends Controller { this.priceInputTargets.forEach(input => { input.addEventListener('input', this.validatePriceInput.bind(this)); + input.addEventListener('keydown', this.restrictDecimalInput.bind(this)); }); } @@ -57,4 +58,13 @@ export default class extends Controller { target.style.borderColor = ""; } } + + restrictDecimalInput(event) { + const target = event.target; + const inputValue = target.value; + + if (inputValue.includes('.') && inputValue.split('.')[1].length >= 2 && !["Backspace", "Delete"].includes(event.key)) { + event.preventDefault(); + } + } } From 67792ab19abcf433f4d04b378af6ee314dba92ae Mon Sep 17 00:00:00 2001 From: igorSimash Date: Wed, 6 Nov 2024 23:41:38 +0200 Subject: [PATCH 11/39] deleted useless rescue & updated design --- app/controllers/application_controller.rb | 6 ------ app/views/errors/internal_server.html.erb | 7 ++++--- app/views/errors/not_found.html.erb | 7 ++++--- app/views/errors/unprocessable.html.erb | 7 ++++--- 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 179e91463..11c87f033 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -7,8 +7,6 @@ class ApplicationController < ActionController::Base before_action :store_user_location!, if: :storable_location? before_action :configure_permitted_parameters, if: :devise_controller? - rescue_from ActiveRecord::RecordNotFound, with: :render_404 - def redirection redirect_to root_url end @@ -48,10 +46,6 @@ def set_i18n_locale_from_params end end - def render_404 - render "errors/not_found", status: :not_found - end - protected def configure_permitted_parameters diff --git a/app/views/errors/internal_server.html.erb b/app/views/errors/internal_server.html.erb index 38c5f22b7..d07c58fff 100644 --- a/app/views/errors/internal_server.html.erb +++ b/app/views/errors/internal_server.html.erb @@ -1,4 +1,5 @@ -
-

<%= t('500_page.title') %> -

<%= t('500_page.description') %>

+
+

500

+

<%= t('500_page.title') %> +

<%= t('500_page.description') %>

diff --git a/app/views/errors/not_found.html.erb b/app/views/errors/not_found.html.erb index 739c59d02..4dc438f8e 100644 --- a/app/views/errors/not_found.html.erb +++ b/app/views/errors/not_found.html.erb @@ -1,4 +1,5 @@ -
-

<%= t('404_page.title') %> -

<%= t('404_page.description') %>

+
+

404

+

<%= t('404_page.title') %> +

<%= t('404_page.description') %>

diff --git a/app/views/errors/unprocessable.html.erb b/app/views/errors/unprocessable.html.erb index 9baff5afb..6f87eea57 100644 --- a/app/views/errors/unprocessable.html.erb +++ b/app/views/errors/unprocessable.html.erb @@ -1,4 +1,5 @@ -
-

<%= t('422_page.title') %> -

<%= t('422_page.description') %>

+
+

422

+

<%= t('422_page.title') %> +

<%= t('422_page.description') %>

From 0a0654ef7edbf3ab4d5d19ea0fa1a6526425466f Mon Sep 17 00:00:00 2001 From: alexy78 Date: Fri, 8 Nov 2024 00:44:04 +0200 Subject: [PATCH 12/39] creates show page for calculators --- .../stylesheets/application.tailwind.css | 1 + .../components/showpage_calculator.css | 17 +++++++++++ .../account/calculators_controller.rb | 2 +- app/views/account/calculators/show.html.erb | 29 +++++++++++++++++++ config/locales/en/en.yml | 6 ++++ config/locales/uk/uk.yml | 6 ++++ 6 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 app/assets/stylesheets/components/showpage_calculator.css create mode 100644 app/views/account/calculators/show.html.erb diff --git a/app/assets/stylesheets/application.tailwind.css b/app/assets/stylesheets/application.tailwind.css index f73f37afc..9a85acf0b 100644 --- a/app/assets/stylesheets/application.tailwind.css +++ b/app/assets/stylesheets/application.tailwind.css @@ -10,5 +10,6 @@ @import "/components/pagination"; @import "/components/breadcrumbs.scss"; @import "/components/description_block"; +@import "/components/showpage_calculator"; @import "/utilities/custom-utilities"; @import "/pages/under_construction" diff --git a/app/assets/stylesheets/components/showpage_calculator.css b/app/assets/stylesheets/components/showpage_calculator.css new file mode 100644 index 000000000..700449958 --- /dev/null +++ b/app/assets/stylesheets/components/showpage_calculator.css @@ -0,0 +1,17 @@ +@layer base { + .main-show-container { + @apply flex flex-col bg-white rounded-lg shadow-md w-full p-6 text-left mt-4; + } + .back-arrow { + @apply rounded mb-4 px-2 flex items-center; + } + .calc-details { + @apply flex flex-col mb-4 px-2; + } + .showpage-buttons { + @apply flex justify-start w-full space-x-4; + } + .showpage-text { + @apply text-gray-900; + } + } \ No newline at end of file diff --git a/app/controllers/account/calculators_controller.rb b/app/controllers/account/calculators_controller.rb index 0c85b334e..d0156447b 100644 --- a/app/controllers/account/calculators_controller.rb +++ b/app/controllers/account/calculators_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class Account::CalculatorsController < Account::BaseController - before_action :calculator, only: [:edit, :update, :destroy] + before_action :calculator, only: [:show, :edit, :update, :destroy] load_and_authorize_resource def index diff --git a/app/views/account/calculators/show.html.erb b/app/views/account/calculators/show.html.erb new file mode 100644 index 000000000..450da2e0d --- /dev/null +++ b/app/views/account/calculators/show.html.erb @@ -0,0 +1,29 @@ +
+ <%= link_to account_calculators_path, class: "back-arrow" do %> + <%= inline_svg "icons/arrow-left.svg", class: "z-1 mr-2 mb-0.5" %> + <%= t('buttons.back') %> + <% end %> + +
+ <%= t('.name') %>: +

<%= @calculator.name %>

+
+ +
+ <%= t('.slug') %>: +

<%= @calculator.slug %>

+
+ +
+ <%= link_to t('.edit'), + edit_account_calculator_path(@calculator.slug, locale: I18n.locale), + class: "btn btn-green" %> + + <%= button_to account_calculator_path(@calculator.slug, locale: I18n.locale), + method: :delete, + data: { turbo_confirm: t('.confirm_delete') }, + class: "btn btn-danger" do %> + <%= t('.delete') %> + <% end %> +
+
\ No newline at end of file diff --git a/config/locales/en/en.yml b/config/locales/en/en.yml index 1828133a0..a1950fcbb 100644 --- a/config/locales/en/en.yml +++ b/config/locales/en/en.yml @@ -439,6 +439,12 @@ en: update_calculator_button: "Update calculator" new: create_calculator_button: "Create calculator" + show: + confirm_delete: "Are you sure you want to delete this calculator?" + name: "Name" + slug: "Slug" + edit: "Edit" + delete: "Delete" feature_flags: submit_button: "Save" new_calculator_design: diff --git a/config/locales/uk/uk.yml b/config/locales/uk/uk.yml index 57ecc3936..67f265a04 100644 --- a/config/locales/uk/uk.yml +++ b/config/locales/uk/uk.yml @@ -326,6 +326,12 @@ uk: prohibited_to_update: " перешкоджають оновленню калькулятора" prohibited_to_save: " перешкоджають збереженню калькулятора" error: "помилки" + show: + confirm_delete: "Ви впевнені, що хочете видалити цей калькулятор?" + name: "Назва" + slug: "Шлях" + edit: "Редагувати" + delete: "Видалити" site_settings: edit: meta-title: "Налаштування сайту" From c2a2fc8688d00563bb784b433e382a20d204bd9d Mon Sep 17 00:00:00 2001 From: alexy78 Date: Fri, 8 Nov 2024 00:47:16 +0200 Subject: [PATCH 13/39] updated spaces in files --- app/assets/stylesheets/components/showpage_calculator.css | 3 ++- app/views/account/calculators/show.html.erb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/components/showpage_calculator.css b/app/assets/stylesheets/components/showpage_calculator.css index 700449958..3fa30a41d 100644 --- a/app/assets/stylesheets/components/showpage_calculator.css +++ b/app/assets/stylesheets/components/showpage_calculator.css @@ -14,4 +14,5 @@ .showpage-text { @apply text-gray-900; } - } \ No newline at end of file + } + \ No newline at end of file diff --git a/app/views/account/calculators/show.html.erb b/app/views/account/calculators/show.html.erb index 450da2e0d..ae104c9c5 100644 --- a/app/views/account/calculators/show.html.erb +++ b/app/views/account/calculators/show.html.erb @@ -26,4 +26,4 @@ <%= t('.delete') %> <% end %>
- \ No newline at end of file + From b24232416950e8f1d19e3caca44a763456319657 Mon Sep 17 00:00:00 2001 From: alexy78 Date: Fri, 8 Nov 2024 00:48:54 +0200 Subject: [PATCH 14/39] updated spaces in files --- app/assets/stylesheets/components/showpage_calculator.css | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/stylesheets/components/showpage_calculator.css b/app/assets/stylesheets/components/showpage_calculator.css index 3fa30a41d..8e3e79c3a 100644 --- a/app/assets/stylesheets/components/showpage_calculator.css +++ b/app/assets/stylesheets/components/showpage_calculator.css @@ -15,4 +15,3 @@ @apply text-gray-900; } } - \ No newline at end of file From 69409c7d72a4480b5da3390e47effe5c34c128ca Mon Sep 17 00:00:00 2001 From: alexy78 Date: Fri, 8 Nov 2024 01:16:19 +0200 Subject: [PATCH 15/39] updated css file --- .../components/showpage_calculator.css | 36 ++++++++++--------- app/views/account/calculators/show.html.erb | 4 +-- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/app/assets/stylesheets/components/showpage_calculator.css b/app/assets/stylesheets/components/showpage_calculator.css index 8e3e79c3a..c106a4e6e 100644 --- a/app/assets/stylesheets/components/showpage_calculator.css +++ b/app/assets/stylesheets/components/showpage_calculator.css @@ -1,17 +1,21 @@ -@layer base { - .main-show-container { - @apply flex flex-col bg-white rounded-lg shadow-md w-full p-6 text-left mt-4; - } - .back-arrow { - @apply rounded mb-4 px-2 flex items-center; - } - .calc-details { - @apply flex flex-col mb-4 px-2; - } - .showpage-buttons { - @apply flex justify-start w-full space-x-4; - } - .showpage-text { - @apply text-gray-900; - } +@layer components { + .main-show-container { + @apply flex flex-col bg-white rounded-lg shadow-md w-full p-6 text-left mt-4; } + + .back-arrow { + @apply rounded mb-4 px-2 flex items-center; + } + + .calc-details { + @apply flex flex-col mb-4 px-2; + } + + .showpage-buttons { + @apply flex justify-start w-full space-x-4; + } + + .showpage-text { + @apply text-slate-600 text-sm; + } +} diff --git a/app/views/account/calculators/show.html.erb b/app/views/account/calculators/show.html.erb index ae104c9c5..9a6fdaaa9 100644 --- a/app/views/account/calculators/show.html.erb +++ b/app/views/account/calculators/show.html.erb @@ -5,12 +5,12 @@ <% end %>
- <%= t('.name') %>: + <%= t('.name') %>:

<%= @calculator.name %>

- <%= t('.slug') %>: + <%= t('.slug') %>:

<%= @calculator.slug %>

From 7ffda2b4809ba162dabd0756c62888c8dca7d55b Mon Sep 17 00:00:00 2001 From: OlexanderVanzuriak <48361492+olexandervanzuriak@users.noreply.github.com> Date: Fri, 8 Nov 2024 11:36:49 +0200 Subject: [PATCH 16/39] 906 Fix scroll bug (#919) * Fix scroll bug * make scrolling smooth --- app/javascript/controllers/results_controller.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/javascript/controllers/results_controller.js b/app/javascript/controllers/results_controller.js index 2fc4578ed..f66dbcc75 100644 --- a/app/javascript/controllers/results_controller.js +++ b/app/javascript/controllers/results_controller.js @@ -20,5 +20,7 @@ export default class extends Controller { this.willBuyDiapersPluralizeTarget.innerHTML = result.to_be_diapers_amount_pluralize; this.boughtDiapersPluralizeTarget.innerHTML = result.used_diapers_amount_pluralize; + + this.element.scrollIntoView({ behavior: "smooth" }); } } From 150cb68d9d60872bced6fbc46e07041a42a70e7b Mon Sep 17 00:00:00 2001 From: alexy78 Date: Fri, 8 Nov 2024 13:55:51 +0200 Subject: [PATCH 17/39] updated yml file and seeds --- db/data/categories.yml | 36 +++++++++++++++++++++++++++++ db/seeds.rb | 51 ++++++++++-------------------------------- 2 files changed, 48 insertions(+), 39 deletions(-) create mode 100644 db/data/categories.yml diff --git a/db/data/categories.yml b/db/data/categories.yml new file mode 100644 index 000000000..8c845fa64 --- /dev/null +++ b/db/data/categories.yml @@ -0,0 +1,36 @@ +categories: + - en_name: Budgetary + uk_name: Бюджетна + priority: 1 + periods: + - {period_start: 1, period_end: 3, usage_amount: 10, price: 5.96} + - {period_start: 4, period_end: 6, usage_amount: 8, price: 6.35} + - {period_start: 7, period_end: 9, usage_amount: 6, price: 6.35} + - {period_start: 10, period_end: 12, usage_amount: 6, price: 6.45} + - {period_start: 13, period_end: 18, usage_amount: 4, price: 6.45} + - {period_start: 19, period_end: 24, usage_amount: 4, price: 7.6} + - {period_start: 25, period_end: 30, usage_amount: 2, price: 7.6} + + - en_name: Medium + uk_name: Середня + priority: 2 + periods: + - {period_start: 1, period_end: 3, usage_amount: 10, price: 8.03} + - {period_start: 4, period_end: 6, usage_amount: 8, price: 8.33} + - {period_start: 7, period_end: 9, usage_amount: 6, price: 8.65} + - {period_start: 10, period_end: 12, usage_amount: 6, price: 10.07} + - {period_start: 13, period_end: 18, usage_amount: 4, price: 10.07} + - {period_start: 19, period_end: 24, usage_amount: 4, price: 11.13} + - {period_start: 25, period_end: 30, usage_amount: 2, price: 11.13} + + - en_name: Premium + uk_name: Преміум + priority: 3 + periods: + - {period_start: 1, period_end: 3, usage_amount: 10, price: 11.41} + - {period_start: 4, period_end: 6, usage_amount: 8, price: 12.59} + - {period_start: 7, period_end: 9, usage_amount: 6, price: 14.18} + - {period_start: 10, period_end: 12, usage_amount: 6, price: 18.52} + - {period_start: 13, period_end: 18, usage_amount: 4, price: 18.52} + - {period_start: 19, period_end: 24, usage_amount: 4, price: 18.62} + - {period_start: 25, period_end: 30, usage_amount: 2, price: 18.62} \ No newline at end of file diff --git a/db/seeds.rb b/db/seeds.rb index e3d78a3de..4719c18c8 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -32,45 +32,18 @@ FactoryBot.create(:product, :diaper) unless Product.exists?(title: "diaper") # Categories -def create_category_with_periods(en_name, uk_name, periods) - unless Category.exists?(en_name: en_name) - category = Category.create!(en_name: en_name, uk_name: uk_name) +categories_data = YAML.load_file(Rails.root.join("db", "data", "categories.yml")) - periods.each do |period_data| - DiapersPeriod.create!(period_data.merge(category: category)) - end - end -end - -# Budgetary category periods -create_category_with_periods("Budgetary", "Бюджетна", [ - { period_start: 1, period_end: 3, usage_amount: 10, price: 5.96 }, - { period_start: 4, period_end: 6, usage_amount: 8, price: 6.35 }, - { period_start: 7, period_end: 9, usage_amount: 6, price: 6.35 }, - { period_start: 10, period_end: 12, usage_amount: 6, price: 6.45 }, - { period_start: 13, period_end: 18, usage_amount: 4, price: 6.45 }, - { period_start: 19, period_end: 24, usage_amount: 4, price: 7.6 }, - { period_start: 25, period_end: 30, usage_amount: 2, price: 7.6 } -]) +def create_category_with_periods(*args) + en_name, uk_name, priority, periods = args + return if Category.exists?(en_name: en_name) -# Medium category periods -create_category_with_periods("Medium", "Середня", [ - { period_start: 1, period_end: 3, usage_amount: 10, price: 8.03 }, - { period_start: 4, period_end: 6, usage_amount: 8, price: 8.33 }, - { period_start: 7, period_end: 9, usage_amount: 6, price: 8.65 }, - { period_start: 10, period_end: 12, usage_amount: 6, price: 10.07 }, - { period_start: 13, period_end: 18, usage_amount: 4, price: 10.07 }, - { period_start: 19, period_end: 24, usage_amount: 4, price: 11.13 }, - { period_start: 25, period_end: 30, usage_amount: 2, price: 11.13 } -]) + category = Category.create!(en_name:, uk_name:, priority:) + periods.each { |period| DiapersPeriod.create!(period.merge(category:)) } +end -# Premium category periods -create_category_with_periods("Premium", "Преміум", [ - { period_start: 1, period_end: 3, usage_amount: 10, price: 11.41 }, - { period_start: 4, period_end: 6, usage_amount: 8, price: 12.59 }, - { period_start: 7, period_end: 9, usage_amount: 6, price: 14.18 }, - { period_start: 10, period_end: 12, usage_amount: 6, price: 18.52 }, - { period_start: 13, period_end: 18, usage_amount: 4, price: 18.52 }, - { period_start: 19, period_end: 24, usage_amount: 4, price: 18.62 }, - { period_start: 25, period_end: 30, usage_amount: 2, price: 18.62 } -]) +categories_data["categories"].each do |category_data| + create_category_with_periods( + *category_data.slice("en_name", "uk_name", "priority", "periods").values + ) +end From 02f38d7a12686bd50cdeaf2a2230bd6ca1fd54eb Mon Sep 17 00:00:00 2001 From: alexy78 Date: Fri, 8 Nov 2024 13:57:43 +0200 Subject: [PATCH 18/39] updates spaces in yaml file --- db/data/categories.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/data/categories.yml b/db/data/categories.yml index 8c845fa64..d392be94c 100644 --- a/db/data/categories.yml +++ b/db/data/categories.yml @@ -33,4 +33,4 @@ categories: - {period_start: 10, period_end: 12, usage_amount: 6, price: 18.52} - {period_start: 13, period_end: 18, usage_amount: 4, price: 18.52} - {period_start: 19, period_end: 24, usage_amount: 4, price: 18.62} - - {period_start: 25, period_end: 30, usage_amount: 2, price: 18.62} \ No newline at end of file + - {period_start: 25, period_end: 30, usage_amount: 2, price: 18.62} From aec0e74972701465209bca2e460b4fc5f5326c60 Mon Sep 17 00:00:00 2001 From: alexy78 Date: Fri, 8 Nov 2024 14:36:39 +0200 Subject: [PATCH 19/39] updated seeds --- db/seeds.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/db/seeds.rb b/db/seeds.rb index 4719c18c8..5c502c8c7 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -33,13 +33,12 @@ # Categories categories_data = YAML.load_file(Rails.root.join("db", "data", "categories.yml")) - def create_category_with_periods(*args) en_name, uk_name, priority, periods = args return if Category.exists?(en_name: en_name) - category = Category.create!(en_name:, uk_name:, priority:) - periods.each { |period| DiapersPeriod.create!(period.merge(category:)) } + category = Category.create!(en_name: en_name, uk_name: uk_name, priority: priority) + periods.each { |period| DiapersPeriod.create!(period.merge(category: category)) } end categories_data["categories"].each do |category_data| From 9d84572f18684e6c87387c46f5fd88be6d2f06a4 Mon Sep 17 00:00:00 2001 From: DanielVajnagi <82052651+DanielVajnagi@users.noreply.github.com> Date: Sat, 9 Nov 2024 12:31:59 +0200 Subject: [PATCH 20/39] 931 fix selflock bug (#937) * closes #931-fix-selflock-bug * added validation in controller and test * added request test * before_action added * Update app/controllers/account/users_controller.rb Co-authored-by: Ivan Marynych <49816584+loqimean@users.noreply.github.com> * guard style return * Update app/controllers/account/users_controller.rb Co-authored-by: Ivan Marynych <49816584+loqimean@users.noreply.github.com> * Update users_controller.rb * dig * Update users_controller.rb * Update users_controller.rb * Update users_controller.rb --------- Co-authored-by: Ivan Marynych <49816584+loqimean@users.noreply.github.com> --- app/controllers/account/users_controller.rb | 11 +++++++++ app/views/account/users/index.html.erb | 10 +++++--- config/locales/en/en.yml | 1 + config/locales/uk/uk.yml | 1 + spec/features/account/users_spec.rb | 13 ++++++++++ spec/requests/account/users_spec.rb | 27 +++++++++++++++++++++ 6 files changed, 59 insertions(+), 4 deletions(-) diff --git a/app/controllers/account/users_controller.rb b/app/controllers/account/users_controller.rb index e36afe64d..4fc589e74 100644 --- a/app/controllers/account/users_controller.rb +++ b/app/controllers/account/users_controller.rb @@ -6,6 +6,7 @@ class Account::UsersController < Account::BaseController layout "account" before_action :set_paper_trail_whodunnit + before_action :blocking_admin, only: :update load_and_authorize_resource @@ -71,6 +72,16 @@ def user_params prms end + def blocking_admin + @user = resource + + return if params.dig(:user, :blocked).blank? || !@user.admin? + + flash[:alert] = t("errors.messages.blocked_user_cannot_be_admin") + + redirect_to account_users_path + end + def collection User.ordered_by_email end diff --git a/app/views/account/users/index.html.erb b/app/views/account/users/index.html.erb index 588018bc4..77d2448f3 100644 --- a/app/views/account/users/index.html.erb +++ b/app/views/account/users/index.html.erb @@ -38,10 +38,12 @@ <%= link_to icon("fa-solid", "eye"), account_user_path(id: user.id) %> <%= link_to icon("fa-solid", "edit"), edit_account_user_path(id: user.id) %> - <%= button_to account_user_path(id: user, user: toggle_block_param(user)), - method: :patch, - data: { turbo_confirm: toggle_confirm(user) } do %> - <%= icon("fa-solid", toggle_class(user)) %> + <% unless user.admin? %> + <%= button_to account_user_path(id: user, user: toggle_block_param(user)), + method: :patch, + data: { turbo_confirm: toggle_confirm(user) } do %> + <%= icon("fa-solid", toggle_class(user)) %> + <% end %> <% end %> diff --git a/config/locales/en/en.yml b/config/locales/en/en.yml index a1950fcbb..00f2143e6 100644 --- a/config/locales/en/en.yml +++ b/config/locales/en/en.yml @@ -822,6 +822,7 @@ en: messages: accepted: "must be accepted" blank: "can't be blank" + blocked_user_cannot_be_admin: "Admin cannot be blocked" confirmation: "doesn't match %{attribute}" empty: "can't be empty" equal_to: "must be equal to %{count}" diff --git a/config/locales/uk/uk.yml b/config/locales/uk/uk.yml index 67f265a04..c77646f31 100644 --- a/config/locales/uk/uk.yml +++ b/config/locales/uk/uk.yml @@ -846,6 +846,7 @@ uk: messages: accepted: "може бути прийнятим" blank: "не може бути порожнім" + blocked_user_cannot_be_admin: "Неможливо заблокувати адміна" confirmation: "не збігається з підтвердженням" empty: "не може бути порожнім" equal_to: "мусить дорівнювати %{count}" diff --git a/spec/features/account/users_spec.rb b/spec/features/account/users_spec.rb index 4ba06de8a..36ce41d8f 100644 --- a/spec/features/account/users_spec.rb +++ b/spec/features/account/users_spec.rb @@ -8,6 +8,7 @@ create(:user, email: "test1@gmail.com", password: "12345878", last_sign_in_at: time_login) end + let!(:admin_user) { create(:user, role: :admin) } include_context :authorize_admin @@ -63,6 +64,7 @@ end accept_confirm { "Are you sure you want to block this user?" } + sleep 3 expect(page).to have_current_path(account_user_path(id: another_user.id)) expect(page).to have_content "Blocked" end @@ -79,11 +81,22 @@ end accept_confirm { "Are you sure you want to unblock this user?" } + sleep 3 expect(page).to have_current_path(account_user_path(id: another_user.id)) expect(page).to have_content "Unblocked" end end + context "when trying to block an admin user" do + it "shows an alert message and redirects to account users path" do + visit account_users_path + + within(:css, "#user-info-#{admin_user.id}") do + expect(page).not_to have_selector("svg.fa-lock-open") # Expect the lock-open button not to be present + end + end + end + context "when edit user`s info correctly" do it "redirects to user info page" do visit edit_account_user_path(id: another_user.id) diff --git a/spec/requests/account/users_spec.rb b/spec/requests/account/users_spec.rb index 2bda4e4e8..05f2afa50 100644 --- a/spec/requests/account/users_spec.rb +++ b/spec/requests/account/users_spec.rb @@ -6,6 +6,7 @@ include_context :authorize_admin let!(:user) { create(:user, last_sign_in_at: Time.current) } + let!(:admin_user) { create(:user, role: :admin) } describe "GET #index" do let(:csv_content) do @@ -117,6 +118,32 @@ end end + describe "PATCH /account/users/:id" do + context "when trying to block an admin user" do + it "sets an alert message and redirects to the admin user account page" do + patch account_user_path(admin_user), params: { user: { blocked: true }} + + expect(response).to redirect_to(account_users_path) + + follow_redirect! + + expect(flash[:alert]).to eq I18n.t("errors.messages.blocked_user_cannot_be_admin") + expect(response.body).to include(I18n.t("errors.messages.blocked_user_cannot_be_admin")) + end + end + + context "when trying to block a non-admin user" do + it "blocks the user successfully" do + patch account_user_path(user), params: { user: { blocked: true }} + + expect(response).to redirect_to(account_user_path(user)) + + follow_redirect! + expect(user.reload.blocked).to be_truthy + end + end + end + describe "DELETE #destroy" do it "destroys the user and redirects to index page" do expect do From 0e601560848e8c4265f15342f398233107f76476 Mon Sep 17 00:00:00 2001 From: igorSimash Date: Sat, 9 Nov 2024 14:10:20 +0200 Subject: [PATCH 21/39] added tw components --- app/assets/stylesheets/application.tailwind.css | 3 ++- app/assets/stylesheets/pages/errors.css | 17 +++++++++++++++++ app/views/errors/internal_server.html.erb | 8 ++++---- app/views/errors/not_found.html.erb | 8 ++++---- app/views/errors/unprocessable.html.erb | 8 ++++---- 5 files changed, 31 insertions(+), 13 deletions(-) create mode 100644 app/assets/stylesheets/pages/errors.css diff --git a/app/assets/stylesheets/application.tailwind.css b/app/assets/stylesheets/application.tailwind.css index f73f37afc..0941e95e7 100644 --- a/app/assets/stylesheets/application.tailwind.css +++ b/app/assets/stylesheets/application.tailwind.css @@ -11,4 +11,5 @@ @import "/components/breadcrumbs.scss"; @import "/components/description_block"; @import "/utilities/custom-utilities"; -@import "/pages/under_construction" +@import "/pages/under_construction"; +@import "/pages/errors"; diff --git a/app/assets/stylesheets/pages/errors.css b/app/assets/stylesheets/pages/errors.css new file mode 100644 index 000000000..bc156a6bd --- /dev/null +++ b/app/assets/stylesheets/pages/errors.css @@ -0,0 +1,17 @@ +@layer components { + .error-section { + @apply mx-auto space-y-4 text-center rounded-lg py-36 bg-white/10 text-success backdrop-blur-md max-w-1230 + } + + .error-section .error-code { + @apply font-bold text-7xl + } + + .error-section .error-title { + @apply text-4xl font-bold + } + + .error-section .error-description { + @apply px-8 text-lg + } +} diff --git a/app/views/errors/internal_server.html.erb b/app/views/errors/internal_server.html.erb index d07c58fff..f8844b9c8 100644 --- a/app/views/errors/internal_server.html.erb +++ b/app/views/errors/internal_server.html.erb @@ -1,5 +1,5 @@ -
-

500

-

<%= t('500_page.title') %> -

<%= t('500_page.description') %>

+
+

500

+

<%= t('500_page.title') %> +

<%= t('500_page.description') %>

diff --git a/app/views/errors/not_found.html.erb b/app/views/errors/not_found.html.erb index 4dc438f8e..ce949101a 100644 --- a/app/views/errors/not_found.html.erb +++ b/app/views/errors/not_found.html.erb @@ -1,5 +1,5 @@ -
-

404

-

<%= t('404_page.title') %> -

<%= t('404_page.description') %>

+
+

404

+

<%= t('404_page.title') %> +

<%= t('404_page.description') %>

diff --git a/app/views/errors/unprocessable.html.erb b/app/views/errors/unprocessable.html.erb index 6f87eea57..a818ccb98 100644 --- a/app/views/errors/unprocessable.html.erb +++ b/app/views/errors/unprocessable.html.erb @@ -1,5 +1,5 @@ -
-

422

-

<%= t('422_page.title') %> -

<%= t('422_page.description') %>

+
+

422

+

<%= t('422_page.title') %> +

<%= t('422_page.description') %>

From bcac592667c1d21efca5652f9674dff1a055eaea Mon Sep 17 00:00:00 2001 From: igorSimash Date: Sat, 9 Nov 2024 14:25:33 +0200 Subject: [PATCH 22/39] fixed capybara test --- spec/features/account/users_spec.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/features/account/users_spec.rb b/spec/features/account/users_spec.rb index 8d6a5e46f..21b9ef8ea 100644 --- a/spec/features/account/users_spec.rb +++ b/spec/features/account/users_spec.rb @@ -117,8 +117,10 @@ describe "user info page" do context "viewing non-existing user" do it "renders the 404 page" do - visit account_user_path(id: 1355) - expect(page).to have_content("Oops! Page Not Found") + expect do + visit account_user_path(id: 1355) + expect(page).to have_content("Oops! Page Not Found") + end.to raise_error(ActiveRecord::RecordNotFound) end end end From 5b84f4da7de2b2307c912d0345a12b64b6218305 Mon Sep 17 00:00:00 2001 From: alexy78 Date: Thu, 14 Nov 2024 19:19:08 +0200 Subject: [PATCH 23/39] updated yml file --- db/data/categories.yml | 105 ++++++++++++++++++++++++++++++++--------- db/seeds.rb | 16 +++---- 2 files changed, 90 insertions(+), 31 deletions(-) diff --git a/db/data/categories.yml b/db/data/categories.yml index d392be94c..c1f24f8ff 100644 --- a/db/data/categories.yml +++ b/db/data/categories.yml @@ -3,34 +3,97 @@ categories: uk_name: Бюджетна priority: 1 periods: - - {period_start: 1, period_end: 3, usage_amount: 10, price: 5.96} - - {period_start: 4, period_end: 6, usage_amount: 8, price: 6.35} - - {period_start: 7, period_end: 9, usage_amount: 6, price: 6.35} - - {period_start: 10, period_end: 12, usage_amount: 6, price: 6.45} - - {period_start: 13, period_end: 18, usage_amount: 4, price: 6.45} - - {period_start: 19, period_end: 24, usage_amount: 4, price: 7.6} - - {period_start: 25, period_end: 30, usage_amount: 2, price: 7.6} + - period_start: 1 + period_end: 3 + usage_amount: 10 + price: 5.96 + - period_start: 4 + period_end: 6 + usage_amount: 8 + price: 6.35 + - period_start: 7 + period_end: 9 + usage_amount: 6 + price: 6.35 + - period_start: 10 + period_end: 12 + usage_amount: 6 + price: 6.45 + - period_start: 13 + period_end: 18 + usage_amount: 4 + price: 6.45 + - period_start: 19 + period_end: 24 + usage_amount: 4 + price: 7.6 + - period_start: 25 + period_end: 30 + usage_amount: 2 + price: 7.6 - en_name: Medium uk_name: Середня priority: 2 periods: - - {period_start: 1, period_end: 3, usage_amount: 10, price: 8.03} - - {period_start: 4, period_end: 6, usage_amount: 8, price: 8.33} - - {period_start: 7, period_end: 9, usage_amount: 6, price: 8.65} - - {period_start: 10, period_end: 12, usage_amount: 6, price: 10.07} - - {period_start: 13, period_end: 18, usage_amount: 4, price: 10.07} - - {period_start: 19, period_end: 24, usage_amount: 4, price: 11.13} - - {period_start: 25, period_end: 30, usage_amount: 2, price: 11.13} + - period_start: 1 + period_end: 3 + usage_amount: 10 + price: 8.03 + - period_start: 4 + period_end: 6 + usage_amount: 8 + price: 8.33 + - period_start: 7 + period_end: 9 + usage_amount: 6 + price: 8.65 + - period_start: 10 + period_end: 12 + usage_amount: 6 + price: 10.07 + - period_start: 13 + period_end: 18 + usage_amount: 4 + price: 10.07 + - period_start: 19 + period_end: 24 + usage_amount: 4 + price: 11.13 + - period_start: 25 + period_end: 30 + usage_amount: 2 + price: 11.13 - en_name: Premium uk_name: Преміум priority: 3 periods: - - {period_start: 1, period_end: 3, usage_amount: 10, price: 11.41} - - {period_start: 4, period_end: 6, usage_amount: 8, price: 12.59} - - {period_start: 7, period_end: 9, usage_amount: 6, price: 14.18} - - {period_start: 10, period_end: 12, usage_amount: 6, price: 18.52} - - {period_start: 13, period_end: 18, usage_amount: 4, price: 18.52} - - {period_start: 19, period_end: 24, usage_amount: 4, price: 18.62} - - {period_start: 25, period_end: 30, usage_amount: 2, price: 18.62} + - period_start: 1 + period_end: 3 + usage_amount: 10 + price: 11.41 + - period_start: 4 + period_end: 6 + usage_amount: 8 + price: 12.59 + - period_start: 7 + period_end: 9 + usage_amount: 6 + price: 14.18 + - period_start: 10 + period_end: 12 + usage_amount: 6 + price: 18.52 + - period_start: 13 + period_end: 18 + usage_amount: 4 + price: 18.52 + - period_start: 19 + period_end: 24 + usage_amount: 4 + price: 18.62 + - period_start: 25 + period_end: 30 + usage_amount: 2 + price: 18.62 \ No newline at end of file diff --git a/db/seeds.rb b/db/seeds.rb index 5c502c8c7..39c27156f 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -31,18 +31,14 @@ FactoryBot.create(:product, :diaper) unless Product.exists?(title: "diaper") -# Categories categories_data = YAML.load_file(Rails.root.join("db", "data", "categories.yml")) -def create_category_with_periods(*args) - en_name, uk_name, priority, periods = args + +categories_data["categories"].each do |category_data| return if Category.exists?(en_name: en_name) - category = Category.create!(en_name: en_name, uk_name: uk_name, priority: priority) - periods.each { |period| DiapersPeriod.create!(period.merge(category: category)) } -end + category = Category.create!(category_data.slice("en_name", "uk_name", "priority")) -categories_data["categories"].each do |category_data| - create_category_with_periods( - *category_data.slice("en_name", "uk_name", "priority", "periods").values - ) + period_records = category_data["periods"].map { |period_data| period_data.merge("category_id" => category.id) } + + DiapersPeriod.insert_all(period_records) end From 03b2931074c08aae3758fcbe27ff48855f4c2044 Mon Sep 17 00:00:00 2001 From: alexy78 Date: Fri, 15 Nov 2024 00:00:53 +0200 Subject: [PATCH 24/39] changed seeds for categories --- db/seeds.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/db/seeds.rb b/db/seeds.rb index 39c27156f..10c85e359 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -31,14 +31,15 @@ FactoryBot.create(:product, :diaper) unless Product.exists?(title: "diaper") +# Categories categories_data = YAML.load_file(Rails.root.join("db", "data", "categories.yml")) categories_data["categories"].each do |category_data| - return if Category.exists?(en_name: en_name) + next if Category.exists?(en_name: category_data["en_name"]) - category = Category.create!(category_data.slice("en_name", "uk_name", "priority")) + periods = category_data.delete("periods") + category = Category.create!(**category_data) - period_records = category_data["periods"].map { |period_data| period_data.merge("category_id" => category.id) } - - DiapersPeriod.insert_all(period_records) + periods.map! { |period_data| period_data.merge(category_id: category.id) } + periods.each { |period_data| DiapersPeriod.create!(**period_data) } end From c135b4730f7586bf87b24b39311daab741283b52 Mon Sep 17 00:00:00 2001 From: alexy78 Date: Fri, 15 Nov 2024 00:05:12 +0200 Subject: [PATCH 25/39] added extra space to yml file --- db/data/categories.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/data/categories.yml b/db/data/categories.yml index c1f24f8ff..c939cf1b1 100644 --- a/db/data/categories.yml +++ b/db/data/categories.yml @@ -96,4 +96,4 @@ categories: - period_start: 25 period_end: 30 usage_amount: 2 - price: 18.62 \ No newline at end of file + price: 18.62 From e974077de2007cd3139f0f1e8a5f2513997b7fa8 Mon Sep 17 00:00:00 2001 From: SleekMutt <84468904+SleekMutt@users.noreply.github.com> Date: Mon, 18 Nov 2024 17:01:42 +0200 Subject: [PATCH 26/39] 910 Created menstrual hygiene products calculator (#924) * Created menstrual hygiene calculator. Created corresponding to it view, api controller, service and validator. Also added new corresponding action to render mhc calculator to calculators controller. * Fixed link and rubocop offences * Updated rubocop target version and extracted methods in pad usage service * Deleted redundant skip * Added tests for api controller * Fixed some files * Removed redundant code from controllers action * Added smooth scroll * Fixed localization with links * Fixed rubocops target version * Fixed typo * Fixed CI * Refactored code for a better style * Added tests for validator * Fixed offenses * Added tests for each of the validate param method * Retrieved shared example to separate file * Added before hook for blank param --------- Co-authored-by: Ivan Marynych <49816584+loqimean@users.noreply.github.com> Co-authored-by: loqimean --- .rubocop.yml | 2 +- app/assets/images/pad_scales.png | Bin 0 -> 23169 bytes app/assets/images/pads_bought.png | Bin 0 -> 4761 bytes app/assets/images/pads_to_buy.png | Bin 0 -> 5005 bytes .../api/v1/pad_calculators_controller.rb | 20 ++++ app/controllers/calculators_controller.rb | 5 + app/helpers/calculators_helper.rb | 9 ++ .../controllers/mhc_calculator_controller.js | 71 +++++++++++++ .../controllers/pad_results_controller.js | 21 ++++ app/services/calculators/pad_usage_service.rb | 52 ++++++++++ app/validators/mhc_calculator_validator.rb | 55 +++++++++++ app/views/calculators/mhc_calculator.erb | 93 ++++++++++++++++++ .../layouts/_mhc_description_block.html.erb | 14 +++ config/locales/en/en.yml | 43 ++++++-- config/locales/uk/uk.yml | 43 ++++++-- config/routes.rb | 3 + spec/features/account/users_spec.rb | 2 +- spec/requests/calculators_spec.rb | 10 ++ spec/requests/pad_calculators_spec.rb | 45 +++++++++ .../calculators/pad_usage_service_spec.rb | 29 ++++++ .../shared/presence_validation_example.rb | 17 ++++ .../mhc_calculator_validator_spec.rb | 65 ++++++++++++ 22 files changed, 577 insertions(+), 22 deletions(-) create mode 100644 app/assets/images/pad_scales.png create mode 100644 app/assets/images/pads_bought.png create mode 100644 app/assets/images/pads_to_buy.png create mode 100644 app/controllers/api/v1/pad_calculators_controller.rb create mode 100644 app/javascript/controllers/mhc_calculator_controller.js create mode 100644 app/javascript/controllers/pad_results_controller.js create mode 100644 app/services/calculators/pad_usage_service.rb create mode 100644 app/validators/mhc_calculator_validator.rb create mode 100644 app/views/calculators/mhc_calculator.erb create mode 100644 app/views/layouts/_mhc_description_block.html.erb create mode 100644 spec/requests/pad_calculators_spec.rb create mode 100644 spec/services/calculators/pad_usage_service_spec.rb create mode 100644 spec/support/shared/presence_validation_example.rb create mode 100644 spec/validators/mhc_calculator_validator_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index 51dec8a54..0d435ded4 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -18,7 +18,7 @@ inherit_gem: AllCops: SuggestExtensions: true NewCops: enable - TargetRubyVersion: 3.0.6 + TargetRubyVersion: 3.2 Layout/SpaceInsideHashLiteralBraces: Enabled: true diff --git a/app/assets/images/pad_scales.png b/app/assets/images/pad_scales.png new file mode 100644 index 0000000000000000000000000000000000000000..bae34308887bc2059324a671555d0653f70ccbfd GIT binary patch literal 23169 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 ayLX>r4wl5D9=bDtpRvA$-g6yS!v6uzY>xu~ literal 0 HcmV?d00001 diff --git a/app/assets/images/pads_bought.png b/app/assets/images/pads_bought.png new file mode 100644 index 0000000000000000000000000000000000000000..7a864b55a8b355eddb94bb6ca41a78100054f907 GIT binary patch literal 4761 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 literal 0 HcmV?d00001 diff --git a/app/assets/images/pads_to_buy.png b/app/assets/images/pads_to_buy.png new file mode 100644 index 0000000000000000000000000000000000000000..0fcf41cda70e8e3a161c4df2ec15f9990196b105 GIT binary patch literal 5005 zcmV;86LRc{P)Px|M@d9MRCr$9T?=qj*LnW`doOw|LIMHdWtWGgm58y8DV@4CS_vdl61$GuX`4=y zH0d;r-PDAK1Z_=f>|`7#5*X6@)g(>p$vAD&X|NNc7=g7Cu`^SbfbAk7Bs<{vL5Ph6 zLaW#AIn{r)XE}Rx-@AfU?~F#$J?Eas|DFGR&bJX{}ymp!N^QS%h<(4G2c6 zA>>2AwRbqcf1m;T8fv5OJ9UDAMiAhkuCA^cMEtm>X*YAuYXUTp_6Qo zRYfyxZz`T|jz-qP7(4?2H<;~n06=~T051ZE5dbJagn1kwKiNOyfImV5{?ZP(DFsY` zgouBxa89=Aa|Qs~$2oOhMMcGlY&RaHy{6X4;~Ky{8~_2Kk8^yS6^?yv=j|^IsO^~? z2)GM4{2?Id2r$BQ{^`c`9p6{$P6=QHl&oX7-J7!ZQz<)BhLcB?;}7=%h%U!t$y+6e&^ZLT5~@F|B5L^IlrfIW5-jXjjRB6 z`t<2r0N~MU#8GbaAdb7cs;Y{_=9FH0S|j&zfX9LJ(LCK)xp~8jZ>SwMwbpDw;EyKn z4>Z)b&$1d+b8F3)0r)Ne&X~s6?$~hD1U_THPMFK2@7l7y<2kGLy#hwh?rE()%mFt5Ku<$`dqqZob#-+u9k=*?2mr)g zSM@?f5*4<6I+T-`tepyOYi_N60s!v;gpP)~_K4l!`~bH1$oj=B5q}F1H9)+(p)PtL z5QlYlcYhoJNLR@|(^Um6gU-l*`)oKIK9E5hX^)zQMw|}-hXT#eZrrxM?PN9pqc)lk zN4^6bJ_E=*8tOVCz5(m$>3M)Lwp$E{RywU{x65liw|0)d^+R|YS{$pD#&3mn%kOcl zlT8uXbU1Pwa(Edyyw|X?eUVegemIQUNwp0CT4=P$ozhb80xUuNL?Qv>6L$q{9N<(P zA+EfDEKmGLZ~c`UmX_srT=$gi__77so$>Ft-E@r?1UlY$3@*-i)w1eu&DsI8XD$I9XFy-<9r#(FX$f0i zcV7R`miMy(nlR;M6TG=K@*V&z@G?iMI*8id?R9P2O|f_=LcxHWIpTeu03)#C@%V1R z6$BD-7I7Heqrd6Ome>;$u;MB^!mE>x_U?y)ymVDERiHH=iu^YqtR)WERu}z=N#xeZ zC;-d|3K#?aarqlg%B1+1s1X9Z)d*GdNoqF*hrtiW*tOq93x z{u5lvdo8t2Y&eZF1GPb7kyawHsWtL*1eiZ*adIdEgxPc-0TLYGoLYwiBp9+U?yNg% zTC&Zpk#68n;V2Tjy1L4AT|dq_zfN!$i9X@~Oa)LhIIkR`f*>YsXJmK;?My5dTkSV> z({!X}Co=vB13a*!K6%G0b&oIXR*wj-R!U*dFe-`u4fm1gyEW z_9Fo7CjgkxLLpI;8!(^_+Y1HTIpS{mxwKbi&@fpy}QMf##%WT`h;`A*Vn+`=DLWD0N z;HmBP(W-0!mgL;y4npfWYpGH^ z`r%3Dwmi87Y(1=9`UzXGO2^i~x)q;yWUw>8J_fH3r7{wefDxcNwUtcGq#`0!xa-uZQ@856{w!l` zF#$B17=%1MU!&!DE@a@$Pa}uv^qBl9A{#Y!w%#Q)G_ekVU z#^5;$O{1>YZd>2ht`g5HV9l+Of1#Ks0F3Eztk}9KYNm%$9G2v+uO;VyvJ4bqE#M$A`(FIrxt08(NAew{o|E*#YAbF!IeP0qU8{XgsN?J#fZB|I^0$ zwzM?=*AG>fBkC)Y#2;>`Z+k(W2XR($f_;~B{1cOpK)bWCzU_HY#{`VHtGBnem~-Aq z+$9PbkAtpxch>!YMSZvt&aFHsVDAsSl?r3IaI9}Gn*c5HZ2&$ zXG3n|rA<2+hwpQ4X6!lF`6twU9Re`@_I3EEp(gjq;k9QjK+uIg?Gsiwl+LsiT9yMpRmq3=UXc)EBE>Bd{1l5 z7S4GyZK@g19LEvxP#mFoXI*=n+!pCRK3MQa0Bj2fSZ($R@Bt!hYN%^}MeWxFjKtxJ z6)U2NMB=w8kQ>A~mQ8mzJ>#ag+{(QYFDPJg`?Q^|qN1YKYoBsG%0kcJ@u!i)?*W1t zaGglKVhn&MIA9(E$mZ}61HRt~jXh+?ktSe0Jv|?e$K(HNHi&5c_%-9ba@V>39(d(a zQ)*r+xbO9K!~YEIu`TyQIJ+^)V{lts)L$$Xo9)N=P{%l7LFR8q;CFH4E0ALma4Nlh)xEG)?B@H|=~GEVtmQ+@T;s@#u|EbHjvwkS~?9 ztU^OFAwRa{$yC}V$i%W>)~CA7L@WjwyVEl!V7x z-Cc5>%DhiQ`2utO1iFsC`6TokSGEJHoGSWS0w06b*Wcx(kN$8tY)06x^@YjJpDD{l z0Op6g>;R)So;&-wv^iRF)=O`G6D}B645Ca#p_{?tR~(ALBjV~Hsh4a6Owo-kh$uET zW{tyCrrr6s(?eFT<(P^rK%^ zV9D`90eU9ZC$$j{hh2A>O&Mz?8zv4*ohndwN4a&bu3oE5F}0!A3+iX7?c7?t1s2aX zGZksqv9mvbcd<3?UZ!B>9AGk7g1ejm^CD`j{IH-A46k}mtyeSeVJI$i+!y1vE>q<> z$ze1)v6I(sCSJhu>W8_BTt9lx%eXwFlT;7#_;LlXUx*{Fg3E+iZ#6k}ue+6~QPcWC z^_2?NocLP!-R0?#6KY#dNXlc*h8UFE%Zc!mX(h^Ml8G~jP=;~kRT450+(J9*Di4}` zRCV8P7H@}zv%{0tkY8ur4~HprPlUr^Gr@Q*F9M8CQKlS1!Ck6OQwiu6NeIeSf*3Lo zUcnN6nA@TXLhxLP*b`u6Bb#TM+?;2xDT{1^WhDmP^x9MamNTI#Iru98lOy(GHOeO+ z?a~!g*UP>^rxmXHS!!QHBbVUtuNu;ta2ijx^jOfn9YMI+>6uh|MGi+@V?3&>-8ly|(SA9@2}tii(Q*1W4*{E1e<}GMmJa7lm6! zx_-f

^T6$#p73Vy62#{D9)fVVrZVySw{E0EnnbOF3^Ph(O0wKU6yLt}8Ewh}VI- z85wf99cN{GOrpJ9F*s;Ev_(Mc>gw99Y1)^pPF0F~f?#U+DI2TV&Ea(`R~Ln-A0&-p zwZL-4;79@1+1XiZ7{*~SSQ3k>^DKy+WhIX^GXK8Dy{EFZ>e>iEJ!7ZYrnkp9Q6lKi~sf||VxV&5&ul+GAmz<%De=B-3aa0hFoqJm3In-dEE?a zWfcQJ&T*FjjP@yL$BrG-N=iz!fq{XtP$+a#=rZwc4l#MR!wOF64jDiU%wOGBfC|g9%Ry%$ch|tgSGfD2sSy7l0 zi85fsX@bi_p^!c>FtC|({?$n+C*MG-5_{%Y71XZq_SPROU6+ZK=LayMvt)xRDWY@c z;GW%lQX3n#7cT!Y?Wn9gplk(c zy?`cfG!lT(Z^qa|m6etBPDuN=KYQt5it9{zb&)VYgqa@-g^a$wzV89R9b#qDm}Do@ z&dBgG_N&EJG!_}Gl!%!)(|w?#qT&y79W2wVmx-F>G82zzG>QVWix)4J<>%+OAmS=f z-)b8Hm8nq-E6{wLeDs33r#kXtGbf%D>*F8rIWaOavVPU7RrJ2ZoaU}cfJyqyEFJ}(;od_WL zXr`EKX=%|*OG~w~va(%_G4oB7!sJ(@-F6)3mEb|@$%c^xOzIf0aMpEQ|3-Ov`8HB( z$$B|23Oxa4!EIV+XQy`Y;zfPVoH_Raz~3dms+Sflmb2AY13207m35Uoj^y9;rN4%7 zI81vnryW?fBGGDfO9lz~Y9t=D;o;%sd3kvUDN`*IN(M;~%`V^MN8ibH#d$Uv6szM> zNt2x4GfC?V#= zZwnDk-&%P%kx1-cxpHNKz?u%$pq)wxbfQqvZF);f3nf68<>%+$KmOYEPYV4Z2Y9U< zZ&l}5JttzB0;uG|G3O(IRz;sEV~jm<`SRt5LZQ$F0oHWWSt@XW0JGw@&dyH#{Q2|R z+_`g?8HREH_&dkUoLWj*N*~mbqT*(;EG}iyDo)VI7M?l*InF8c6aYLEkH;T7fByWN z^XJcJD_5?hmvEVe#dPK_-y+dXwL#V+*s_p?o#Hkl&u{|i`I0Zb5G5tC*RnS8hW{Cu1@ zZ{E#`MB-D3csn`Xw(@x*rk4U%h*a6JmP2BQc$9Pg(|A07G#-z43=R&mBv90d0E$m{ zu1b?scEk&EU$!KZ&zo+**|TS{u&|IqUG$R2l9@AS(zl$eHBDQEh}R`KFzw~Qiq503 z(OJ&<>i|ICjgOmkxC3+V&~Jx9N-`dzbT4c(W2roFDRqwoI# XI}cf { + const targetKey = errorKey.replace(/_([a-z])/g, (match, letter) => letter.toUpperCase()); + const feedbackDiv = document.createElement('div'); + + feedbackDiv.className = 'invalid-feedback'; + feedbackDiv.textContent = errors[errorKey]; + + this[`${targetKey}Target`].classList.add("is-invalid"); + this[`${targetKey}Target`].insertAdjacentElement('afterend', feedbackDiv); + }); + } + + clearErrors(){ + this.constructor.targets.forEach(targetKey => { + const targetElement = this[`${targetKey}Target`]; + targetElement.classList.remove("is-invalid"); + + const feedbackDiv = targetElement.nextElementSibling; + if (feedbackDiv && feedbackDiv.classList.contains('invalid-feedback')) { + feedbackDiv.remove(); + } + }); + } +} diff --git a/app/javascript/controllers/pad_results_controller.js b/app/javascript/controllers/pad_results_controller.js new file mode 100644 index 000000000..a998666df --- /dev/null +++ b/app/javascript/controllers/pad_results_controller.js @@ -0,0 +1,21 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = [ + "padsUsed", + "padsToBeUsed", + "moneySpent", + "moneyWillBeSpent" + ]; + + showResults(data) { + let result = data; + + this.moneySpentTarget.innerHTML = Math.ceil(result.already_used_products_cost); + this.moneyWillBeSpentTarget.innerHTML = Math.ceil(result.products_to_be_used_cost); + this.padsUsedTarget.innerHTML = Math.ceil(result.already_used_products); + this.padsToBeUsedTarget.innerHTML = Math.ceil(result.products_to_be_used); + + this.element.scrollIntoView({ behavior: "smooth" }); + } +} diff --git a/app/services/calculators/pad_usage_service.rb b/app/services/calculators/pad_usage_service.rb new file mode 100644 index 000000000..5de628434 --- /dev/null +++ b/app/services/calculators/pad_usage_service.rb @@ -0,0 +1,52 @@ +class Calculators::PadUsageService + attr_accessor :user_age, :menstruation_age, :menopause_age, + :average_menstruation_cycle_duration, + :pads_per_cycle, :pad_category + + PAD_PRICES = { + budget: 2, + average: 4, + premium: 7 + } + + def initialize(user_age:, menstruation_age:, menopause_age:, average_menstruation_cycle_duration:, + pads_per_cycle:, pad_category:) + @user_age = user_age + @menstruation_age = menstruation_age + @menopause_age = menopause_age || 48.7 + @average_menstruation_cycle_duration = average_menstruation_cycle_duration + @pads_per_cycle = pads_per_cycle + @pad_category = (pad_category || :budget).to_sym + end + + def calculate + { + already_used_products:, + already_used_products_cost:, + products_to_be_used:, + products_to_be_used_cost: + } + end + + private + + def already_used_products + menstruations_from_age_range(menstruation_age, user_age) * pads_per_cycle + end + + def products_to_be_used + menstruations_from_age_range(user_age, menopause_age) * pads_per_cycle + end + + def already_used_products_cost + already_used_products * PAD_PRICES[pad_category] + end + + def products_to_be_used_cost + products_to_be_used * PAD_PRICES[pad_category] + end + + def menstruations_from_age_range(from_age, till_age) + (till_age - from_age) * (365 / average_menstruation_cycle_duration) + end +end diff --git a/app/validators/mhc_calculator_validator.rb b/app/validators/mhc_calculator_validator.rb new file mode 100644 index 000000000..cd5e21bfe --- /dev/null +++ b/app/validators/mhc_calculator_validator.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +class MhcCalculatorValidator + attr_reader :params, :errors + + def initialize(params) + @params = params + @errors = {} + end + + def valid? + validate_user_age + validate_menstruation_age + validate_menopause_age + validate_average_menstruation_cycle_duration + validate_pads_per_cycle + validate_pad_category + + errors.empty? + end + + private + + def validate_user_age + presence_valid?(:user_age) + end + + def validate_menstruation_age + presence_valid?(:menstruation_age) + end + + def validate_menopause_age + presence_valid?(:menopause_age) + end + + def validate_average_menstruation_cycle_duration + presence_valid?(:average_menstruation_cycle_duration) + end + + def validate_pads_per_cycle + presence_valid?(:pads_per_cycle) + end + + def validate_pad_category + presence_valid?(:pad_category) + end + + def presence_valid?(param) + return true if @params[param].present? + + @errors[param] = I18n.t("calculators.errors.presence_error_msg", field: I18n.t("calculators.mhc_calculator.form.#{param}")) + + false + end +end diff --git a/app/views/calculators/mhc_calculator.erb b/app/views/calculators/mhc_calculator.erb new file mode 100644 index 000000000..16cd4d748 --- /dev/null +++ b/app/views/calculators/mhc_calculator.erb @@ -0,0 +1,93 @@ +

+

<%= t(".calculator_name") %>

+
+ <%= simple_form_for(:pad, url: "#", html: { class: "simple_form_calculator", + data: { controller: "mhc-calculator", + mhc_calculator_url_value: api_v1_pad_calculators_url, + mhc_calculator_pad_results_outlet: ".result" } }) do |form| %> +
+ +
+ <%= label_tag "child_product_category", t(".form.user_age") %> +
+ + <%= form.input_field :user_age, + type: "number", + class: "form_fild price_select rounded w-100 form-control mb-0", + data: { mhc_calculator_target: "userAge" } %> + +
+ <%= label_tag "child_product_category", t(".form.menstruation_age") %> +
+ + <%= form.input_field :menstruation_age, + type: "number", + class: "form_fild price_select rounded w-100 form-control mb-0 ", + data: { mhc_calculator_target: "menstruationAge" } %> + +
+ <%= label_tag "child_product_category", t(".form.menopause_age") %> +
+ + <%= form.input_field :menopause_age, + type: "number", + class: "form_fild price_select rounded w-100 form-control mb-0", + data: { mhc_calculator_target: "menopauseAge" } %> + +
+ <%= label_tag "child_product_category", t(".form.average_menstruation_cycle_duration") %> +
+ + <%= form.input_field :average_menstruation_cycle_duration, + type: "number", + class: "form_fild price_select rounded w-100 form-control mb-0", + data: { mhc_calculator_target: "averageMenstruationCycleDuration" } %> + +
+ <%= label_tag "child_product_category", t(".form.pads_per_cycle") %> +
+ + <%= form.input_field :pads_per_cycle, + type: "number", + class: "form_fild price_select rounded w-100 form-control mb-0", + data: { mhc_calculator_target: "padsPerCycle" } %> + +
+ <%= label_tag "child_product_category", t(".form.pad_category") %> +
+ + <%= form.input_field :product_category, + collection: [[t(".form.budgetary"), :budget], [t(".form.average"), :average], [t(".form.premium"), :premium]], + selected: "Budget", + class: "form_fild price_select rounded w-100 form-control mb-0", + data: { mhc_calculator_target: "padCategory" } %> + + <%= form.submit t("calculators.buttons.calculate"), + class: "calculate-btn result-btn mt-6", + data: { action: "mhc-calculator#submit" } %> +
+ <% end %> + + <%= image_tag "pad_scales.png", class: "scales_img", alt: "Scales" %> +
+
+ +
+ <% mhc_calculator_items.each do |item| %> + <% if item == "arrow" %> +
+ <%= image_tag "icons/vector_5.png", class: "vector", alt: "horizontal arrow" %> + <%= image_tag "icons/vector_2.png", class: "vector-mobile", alt: "vertical arrow" %> +
+ <% else %> +
+ <%= image_tag item[:image], class: "img-margin", alt: "icon" %> +

0

+

<%= item[:unit] %>

+

<%= item[:text] %>

+
+ <% end %> + <% end %> +
+ +<%= render "layouts/mhc_description_block" %> diff --git a/app/views/layouts/_mhc_description_block.html.erb b/app/views/layouts/_mhc_description_block.html.erb new file mode 100644 index 000000000..51d89f1b9 --- /dev/null +++ b/app/views/layouts/_mhc_description_block.html.erb @@ -0,0 +1,14 @@ +
+
+
+

+ <%= t(".mhc_description_block_html", + information_link: link_to_external(text: t(".information_link_text"), url: t(".information_link") , class: 'description-link'), + contact_link: link_to_external(text: t(".contact_link_text"), url: t(".contact_link"), class: 'description-link')) %> +

+
+
+ <%= link_to_external(text: t('.use_less'), url: t(".use_less_link"), class: 'action-button btn-green') %> +
+
+
diff --git a/config/locales/en/en.yml b/config/locales/en/en.yml index 00f2143e6..4a624b943 100644 --- a/config/locales/en/en.yml +++ b/config/locales/en/en.yml @@ -52,6 +52,24 @@ en: calculators: index: meta-title: "Calculators" + mhc_calculator: + calculator_name: "Menstrual hygiene products calculator" + form: + user_age: "Your age" + menstruation_age: "Age at First Menstruation" + menopause_age: "Age at Menopause" + average_menstruation_cycle_duration: "Average Menstrual Cycle Duration (days)" + pads_per_cycle: "Average Products Used Per Cycle" + pad_category: "Product Category" + budgetary: "Budgetary" + average: "Average" + premium: "Premium" + bought_products: "already used" + will_buy_products: "to be used in the future" + money_spent: "already spent on menstrual hygiene products" + money_will_be_spent: "yet to be spent on menstrual hygiene products" + pieces: "pcs." + unit: "uah" new_calculator: diaper_сalculator: "Diaper calculator" form: @@ -81,6 +99,7 @@ en: year_and_month_error_msg: "Please, select years and months" year_error_msg: "Please, select years" month_error_msg: "Please, select month" + presence_error_msg: "%{field} is missing" show: welcome_header: "Welcome to ZeroWaste" buttons: @@ -127,6 +146,20 @@ en: method_link: https://zerowastelviv.org.ua/diaper-calculator-method-en/ contact_link: https://zerowastelviv.org.ua/contacts/ use_less_link: https://zerowastelviv.org.ua/en/zero-waste-nappies-en/ + mhc_description_block: + mhc_description_block_html: + "These data are approximate, and exact values may vary depending on your individual needs and habits. + Using reusable menstrual products can significantly reduce waste and serve as a cost-effective alternative to disposable options. + You can optimize the use of these products at any stage, which will help lower expenses and increase comfort. + We recommend exploring methods for the effective use of reusable menstrual products and learning about their benefits. + More %{information_link} about the calculations regarding the quantity and cost of these products can be found in our materials + or by %{contact_link} with your questions." + information_link_text: "detailed information" + contact_link_text: "contacting us" + information_link: "https://docs.google.com/document/d/1xu6cch-Ner9OEpU4_TV5suIUsuVcWpgiUuX6QrOgaL4" + contact_link: "https://zerowastelviv.org.ua/en/contacts/" + use_less: "HOW TO USE LESS" + use_less_link: "https://zerowastelviv.org.ua/en/zero-waste-period/" homepage: homepage_title: "Welcome to the diaper calculator" description: @@ -439,12 +472,6 @@ en: update_calculator_button: "Update calculator" new: create_calculator_button: "Create calculator" - show: - confirm_delete: "Are you sure you want to delete this calculator?" - name: "Name" - slug: "Slug" - edit: "Edit" - delete: "Delete" feature_flags: submit_button: "Save" new_calculator_design: @@ -461,9 +488,6 @@ en: email: "Email" password: "Password" shared: - navigation: - donate: "DONATE" - donate_link: "https://zerowastelviv.org.ua/pidtrymaty/" links: forgot_your_password: "Forgot your password?" log_in: "Log In" @@ -822,7 +846,6 @@ en: messages: accepted: "must be accepted" blank: "can't be blank" - blocked_user_cannot_be_admin: "Admin cannot be blocked" confirmation: "doesn't match %{attribute}" empty: "can't be empty" equal_to: "must be equal to %{count}" diff --git a/config/locales/uk/uk.yml b/config/locales/uk/uk.yml index c77646f31..0b52f53aa 100644 --- a/config/locales/uk/uk.yml +++ b/config/locales/uk/uk.yml @@ -21,6 +21,24 @@ uk: calculators: index: meta-title: "Калькулятори" + mhc_calculator: + calculator_name: "Калькулятор менструальних гігієнічних засобів" + form: + user_age: "Ваш вік" + menstruation_age: "Вік початку менструації" + menopause_age: "Вік настання менопаузи" + average_menstruation_cycle_duration: "Середня тривалість менструального циклу (дні)" + pads_per_cycle: "Середня кількість засобів гігієни за цикл" + pad_category: "Категорія засобів гігієни" + budgetary: "Бюджетна" + average: "Середня" + premium: "Преміум" + bought_products: "ви вже використали" + will_buy_products: "ви ще використаєте" + money_spent: "ви вже витратили" + money_will_be_spent: "ви ще витратите" + pieces: "шт." + unit: "грн" new_calculator: diaper_сalculator: "Калькулятор підгузків" form: @@ -58,6 +76,7 @@ uk: year_and_month_error_msg: "Будь ласка, виберіть, скільки років та місяців дитині" year_error_msg: "Будь ласка, виберіть, скільки років дитині" month_error_msg: "Будь ласка, виберіть, скільки місяців дитині" + presence_error_msg: "Поле \"%{field}\" відсутнє" show: welcome_header: "Ласкаво просимо до ZeroWaste" calculate_result_button: "Pозрахувати" @@ -109,6 +128,20 @@ uk: method_link: "https://zerowastelviv.org.ua/diaper-calculator-method/" contact_link: "https://zerowastelviv.org.ua/kontakty/" use_less_link: "https://zerowastelviv.org.ua/zero-waste-nappies/" + mhc_description_block: + mhc_description_block_html: + "Ці дані є орієнтовними, і точні значення можуть варіюватися залежно від ваших індивідуальних потреб та звичок. + Використання багаторазових менструальних засобів може значно зменшити кількість відходів і стати економічно + вигідною альтернативою одноразовим продуктам. Ви можете оптимізувати використання цих засобів на будь-якому етапі, + що дозволить знизити витрати та підвищити комфорт. Ми рекомендуємо ознайомитися з методами ефективного використання + багаторазових менструальних продуктів та їх перевагами. Більш %{information_link} про розрахунки кількості та вартості + цих продуктів можна знайти в наших матеріалах або звернувшись %{contact_link} з вашими питаннями." + information_link_text: "детальну інформацію" + contact_link_text: "до нас" + information_link: "https://docs.google.com/document/d/1xu6cch-Ner9OEpU4_TV5suIUsuVcWpgiUuX6QrOgaL4" + contact_link: "https://zerowastelviv.org.ua/kontakty/" + use_less: "ЯК ЗМЕНШИТИ ВИКОРИСТАННЯ" + use_less_link: "https://zerowastelviv.org.ua/reuse-zero-waste-menstruatsii/" homepage: homepage_title: "Вас вітає калькулятор підгузків" description: "Цей калькулятор дає змогу порахувати, скільки одноразових підгузків ваша дитина вже використала та ще використає до того часу, як перейде на звичайну білизну. А також визначити вартість уже куплених підгузків та суму, яку ви витратите на них у майбутньому." @@ -326,12 +359,6 @@ uk: prohibited_to_update: " перешкоджають оновленню калькулятора" prohibited_to_save: " перешкоджають збереженню калькулятора" error: "помилки" - show: - confirm_delete: "Ви впевнені, що хочете видалити цей калькулятор?" - name: "Назва" - slug: "Шлях" - edit: "Редагувати" - delete: "Видалити" site_settings: edit: meta-title: "Налаштування сайту" @@ -421,9 +448,6 @@ uk: email: "Електронна пошта" password: "Пароль" shared: - navigation: - donate: "Підтримати" - donate_link: "https://zerowastelviv.org.ua/pidtrymaty/" links: forgot_your_password: "Забули пароль?" log_in: "Увійти" @@ -846,7 +870,6 @@ uk: messages: accepted: "може бути прийнятим" blank: "не може бути порожнім" - blocked_user_cannot_be_admin: "Неможливо заблокувати адміна" confirmation: "не збігається з підтвердженням" empty: "не може бути порожнім" equal_to: "мусить дорівнювати %{count}" diff --git a/config/routes.rb b/config/routes.rb index 7de991fc5..c4ef467b8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -31,6 +31,7 @@ get "/sitemap", to: "sitemap#index" get "/calculator", to: "calculators#calculator" + get "/mhc_calculator", to: "calculators#mhc_calculator" post "/receive_recomendations", to: "calculators#receive_recomendations" get "about-us", to: "home#about", as: "about" @@ -78,6 +79,8 @@ end post "/diaper_calculators", to: "diaper_calculators#calculate" + post "/pad_calculators", + to: "pad_calculators#calculate" end namespace :v2 do resources :calculators, only: [] do diff --git a/spec/features/account/users_spec.rb b/spec/features/account/users_spec.rb index 36ce41d8f..d9c51f171 100644 --- a/spec/features/account/users_spec.rb +++ b/spec/features/account/users_spec.rb @@ -92,7 +92,7 @@ visit account_users_path within(:css, "#user-info-#{admin_user.id}") do - expect(page).not_to have_selector("svg.fa-lock-open") # Expect the lock-open button not to be present + expect(page).to have_no_css("svg.fa-lock-open") # Expect the lock-open button not to be present end end end diff --git a/spec/requests/calculators_spec.rb b/spec/requests/calculators_spec.rb index dacbea773..2be0577e0 100644 --- a/spec/requests/calculators_spec.rb +++ b/spec/requests/calculators_spec.rb @@ -110,6 +110,16 @@ end end + describe "GET /mhc_calculator" do + it "renders pad calculator" do + get mhc_calculator_path + + expect(response).to be_successful + expect(response).to render_template(:mhc_calculator) + expect(response.body).to include("results") + end + end + describe "POST #create" do include_context :authorize_admin diff --git a/spec/requests/pad_calculators_spec.rb b/spec/requests/pad_calculators_spec.rb new file mode 100644 index 000000000..c2787f9ea --- /dev/null +++ b/spec/requests/pad_calculators_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Api::V1::PadCalculatorsController, type: :request do + let(:expected_result) do + { + already_used_products: 4420, + already_used_products_cost: 8840, + products_to_be_used: 5460, + products_to_be_used_cost: 10920 + } + end + + let(:valid_params) do + { + user_age: 30, + menstruation_age: 13, + menopause_age: 51, + average_menstruation_cycle_duration: 28, + pads_per_cycle: 20, + pad_category: :budget + } + end + + describe "POST /pad_calculators" do + context "when params are valid" do + it "return expected results" do + post api_v1_pad_calculators_path, params: valid_params, as: :json + + expect(response).to be_successful + expect(response.body).to eq(expected_result.to_json) + end + end + + context "when params are invalid" do + it "return errors" do + post api_v1_pad_calculators_path + + expect(response).to be_unprocessable + expect(JSON.parse(response.body, symbolize_names: true)).to have_key(:errors) + end + end + end +end diff --git a/spec/services/calculators/pad_usage_service_spec.rb b/spec/services/calculators/pad_usage_service_spec.rb new file mode 100644 index 000000000..3948cef44 --- /dev/null +++ b/spec/services/calculators/pad_usage_service_spec.rb @@ -0,0 +1,29 @@ +require "rails_helper" + +RSpec.describe Calculators::PadUsageService do + describe "#calculate" do + let!(:service) do + described_class.new( + user_age: 30, + menstruation_age: 13, + menopause_age: 51, + pads_per_cycle: 20, + average_menstruation_cycle_duration: 28, + pad_category: :budget + ) + end + + before do + service.calculate + end + + let(:result) { service.calculate } + + it "calculates the correct values" do + expect(result[:already_used_products]).to eq(4_420.0) + expect(result[:already_used_products_cost]).to eq(8_840.0) + expect(result[:products_to_be_used]).to eq(5_460.0) + expect(result[:products_to_be_used_cost]).to eq(10_920.0) + end + end +end diff --git a/spec/support/shared/presence_validation_example.rb b/spec/support/shared/presence_validation_example.rb new file mode 100644 index 000000000..7138d9d5d --- /dev/null +++ b/spec/support/shared/presence_validation_example.rb @@ -0,0 +1,17 @@ +shared_examples "presence validation" do |attribute| + it "#{attribute} is valid when present" do + expect(validator.send(:"validate_#{attribute}")).to be true + expect(validator.errors[attribute]).to be_nil + end + + context "#{attribute} is not present" do + before do + validator.params[attribute] = nil + end + + it "#{attribute} is invalid when blank" do + expect(validator.send(:"validate_#{attribute}")).to be false + expect(validator.errors[attribute]).to eq(I18n.t("#{local_error_prefix}.presence_error_msg", field: I18n.t("#{local_field_prefix}.#{attribute}"))) + end + end +end diff --git a/spec/validators/mhc_calculator_validator_spec.rb b/spec/validators/mhc_calculator_validator_spec.rb new file mode 100644 index 000000000..8c4dc67b8 --- /dev/null +++ b/spec/validators/mhc_calculator_validator_spec.rb @@ -0,0 +1,65 @@ +# spec/validators/mhc_calculator_validator_spec.rb + +require "rails_helper" + +RSpec.describe MhcCalculatorValidator do + let(:valid_params) do + { + user_age: 30, + menstruation_age: 13, + menopause_age: 50, + average_menstruation_cycle_duration: 28, + pads_per_cycle: 10, + pad_category: "budget" + } + end + let!(:validator) { described_class.new(valid_params) } + + let(:local_error_prefix) { "calculators.errors" } + let(:local_field_prefix) { "calculators.mhc_calculator.form" } + + describe "#valid?" do + context "when all parameters are valid" do + it "returns true" do + expect(validator.valid?).to be true + expect(validator.errors).to be_empty + end + end + + context "when some parameter is missing" do + let(:invalid_params_validator) { described_class.new(valid_params.except(:user_age)) } + + it "returns false" do + expect(invalid_params_validator.valid?).to be false + expect(invalid_params_validator.errors).to include(:user_age) + expect(invalid_params_validator.errors[:user_age]).to eq(I18n.t("#{local_error_prefix}.presence_error_msg", field: I18n.t("#{local_field_prefix}.user_age"))) + end + end + end + + describe "individual validations" do + context "user age validation" do + include_examples "presence validation", :user_age + end + + context "menstruation age validation" do + include_examples "presence validation", :menstruation_age + end + + context "menopause age validation" do + include_examples "presence validation", :menopause_age + end + + context "average menstruation cycle duration validation" do + include_examples "presence validation", :average_menstruation_cycle_duration + end + + context "pads per cycle validation" do + include_examples "presence validation", :pads_per_cycle + end + + context "pad category validation" do + include_examples "presence validation", :pad_category + end + end +end From d2b28b4dbde431fd446f6941d28ac5ba9259377c Mon Sep 17 00:00:00 2001 From: igorSimash Date: Mon, 18 Nov 2024 18:16:07 +0200 Subject: [PATCH 27/39] deleted test --- spec/features/account/users_spec.rb | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/spec/features/account/users_spec.rb b/spec/features/account/users_spec.rb index 21b9ef8ea..e63ad7b7a 100644 --- a/spec/features/account/users_spec.rb +++ b/spec/features/account/users_spec.rb @@ -113,15 +113,4 @@ expect(page).to have_content "Re-password doesn't match Password" end end - - describe "user info page" do - context "viewing non-existing user" do - it "renders the 404 page" do - expect do - visit account_user_path(id: 1355) - expect(page).to have_content("Oops! Page Not Found") - end.to raise_error(ActiveRecord::RecordNotFound) - end - end - end end From e3ee22d0194f2b85a53a2eda21ed3a3561645a28 Mon Sep 17 00:00:00 2001 From: Andrii Date: Thu, 14 Nov 2024 12:54:49 +0000 Subject: [PATCH 28/39] add branch --- .github/workflows/ci.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a13be3851..77129c821 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,16 @@ name: CI on: push: + pull_request: + branches: + - develop + - master + types: + - closed + + release: + types: [published] + jobs: rubocop: runs-on: ubuntu-latest @@ -50,14 +60,14 @@ jobs: with: ruby-version: 3.3.5 bundler-cache: true - + - name: Install system dependencies run: sudo apt-get install -y libvips42 libvips-dev imagemagick - uses: actions/setup-node@v1 with: - node-version: '14.x' - registry-url: 'https://registry.npmjs.org' + node-version: "14.x" + registry-url: "https://registry.npmjs.org" - uses: nanasess/setup-chromedriver@master From bb41c570eb352056725b1e0b9fb56102285ba002 Mon Sep 17 00:00:00 2001 From: NVMakarenko Date: Tue, 19 Nov 2024 14:56:50 +0200 Subject: [PATCH 29/39] fix refresh newCategory page --- app/views/account/categories/new.html.erb | 18 +++--------------- .../account/categories/new.turbo_stream.erb | 4 ++++ .../categories/partials/new/_form.html.erb | 15 +++++++++++++++ 3 files changed, 22 insertions(+), 15 deletions(-) create mode 100644 app/views/account/categories/new.turbo_stream.erb create mode 100644 app/views/account/categories/partials/new/_form.html.erb diff --git a/app/views/account/categories/new.html.erb b/app/views/account/categories/new.html.erb index f9f6156d6..1bdd33494 100644 --- a/app/views/account/categories/new.html.erb +++ b/app/views/account/categories/new.html.erb @@ -1,15 +1,3 @@ -<%= simple_form_for @category, url: { action: "create" }, html: { novalidate: false } do |f| %> -
-
- <%= f.input :uk_name, class: "form-control col-sm-11" %> - <%= f.input :en_name, class: "form-control col-sm-11" %> - <%= f.input :priority, collection: Category::PRIORITY_RANGE, wrapper: :custom_vertical_select %> -
-
-
- <%= f.submit t(".form.create_category_button"), class: "btn btn-green me-2" %> - <%= link_to account_categories_path, class: "btn btn-danger d-flex align-items-center justify-content-center" do %> - <%= t("buttons.cancel") %> - <% end %> -
-<% end %> +
+ <%= render "account/categories/partials/new/form", category: @category %> +
diff --git a/app/views/account/categories/new.turbo_stream.erb b/app/views/account/categories/new.turbo_stream.erb new file mode 100644 index 000000000..0de8de8e1 --- /dev/null +++ b/app/views/account/categories/new.turbo_stream.erb @@ -0,0 +1,4 @@ +<%= turbo_stream.replace( + dom_id(@category, :form), + partial: "account/categories/partials/new/form", + locals: {category: @category}) %> diff --git a/app/views/account/categories/partials/new/_form.html.erb b/app/views/account/categories/partials/new/_form.html.erb new file mode 100644 index 000000000..f0ad59de7 --- /dev/null +++ b/app/views/account/categories/partials/new/_form.html.erb @@ -0,0 +1,15 @@ +<%= simple_form_for @category, url: { action: "create" }, html: { id: dom_id(category, :form) } do |f| %> +
+
+ <%= f.input :uk_name, class: "form-control col-sm-11" %> + <%= f.input :en_name, class: "form-control col-sm-11" %> + <%= f.input :priority, collection: Category::PRIORITY_RANGE, wrapper: :custom_vertical_select %> +
+
+
+ <%= f.submit t("account.categories.new.form.create_category_button"), class: "btn btn-green me-2" %> + <%= link_to account_categories_path, class: "btn btn-danger d-flex align-items-center justify-content-center" do %> + <%= t("buttons.cancel") %> + <% end %> +
+<% end %> From f00318645322bf2754ab9d347b5098f6070c948e Mon Sep 17 00:00:00 2001 From: Andrii Date: Tue, 19 Nov 2024 14:04:11 +0000 Subject: [PATCH 30/39] add pipeline --- .github/workflows/ci.yml | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 77129c821..315493d3a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -89,3 +89,39 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} json-path: tmp/rspec_results.json if: always() + + deploy-to-develop: + needs: rspec + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == true + steps: + - uses: actions/checkout@v2 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.3.5 + bundler-cache: true + + - uses: miloserdow/capistrano-deploy@v3 + with: + target: development + deploy_key: ${{ secrets.DEV_DEPLOY_KEY }} + + deploy-to-production: + needs: rspec + runs-on: ubuntu-latest + if: github.event_name == 'release' && github.event.action == 'published' + steps: + - uses: actions/checkout@v2 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.3.5 + bundler-cache: true + + - uses: miloserdow/capistrano-deploy@v3 + with: + target: production + deploy_key: ${{ secrets.PROD_DEPLOY_KEY }} From 123bc949c02a2df5100628d6bfc2042d3e603b7c Mon Sep 17 00:00:00 2001 From: Andrii Date: Wed, 20 Nov 2024 09:30:59 +0000 Subject: [PATCH 31/39] add key for staging --- .github/workflows/ci.yml | 3 ++- config/credentials/staging_deploy_id_ed25519_enc | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 config/credentials/staging_deploy_id_ed25519_enc diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 315493d3a..6b7191454 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -90,7 +90,7 @@ jobs: json-path: tmp/rspec_results.json if: always() - deploy-to-develop: + deploy-to-staging: needs: rspec runs-on: ubuntu-latest if: github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == true @@ -107,6 +107,7 @@ jobs: with: target: development deploy_key: ${{ secrets.DEV_DEPLOY_KEY }} + enc_rsa_key_pth: config/credentials/deploy_id_ed25519_enc deploy-to-production: needs: rspec diff --git a/config/credentials/staging_deploy_id_ed25519_enc b/config/credentials/staging_deploy_id_ed25519_enc new file mode 100644 index 000000000..bd3eb4dc7 --- /dev/null +++ b/config/credentials/staging_deploy_id_ed25519_enc @@ -0,0 +1,10 @@ +U2FsdGVkX1+dAao58N25fwh/ICC4DEgTajI1FHPj36c7HRFb0WUo9mDljBWNEqA7 +R0+5nXn3pROcIFNjSu6wInKiCXB6wHTN+N29k+I6tG5HDkSElt7OZJffeluFfz/N +XKtGdPZuXjACLE343yCclrde9MJBZBsFVujONQfj4DMcrFkUmaPSGQyKnt+zahl7 +ML8Q2tnF8eFdFk9DyOqEha0F+Ux/+ZJtAjL0e8bYQQZ1Oz8vmqWgradu0V5WL37x +5mXo9BAEfb+oxOjOaN29eIFcTWGP6d0sXtfT3/815e6FKCphZ2v7A3eaKqb/s0Zi +LF+RKd+WMXGS2kB30ySZmJ6mX+uFbn2Qfq7lyy81qGwO1hfbAUvEqLe/YE5UaDDi +d8II5hh1SOitUbEU8pKjDPhuYdYZJggRa0WEC9P5rpC5JK750IPok4qFlc5UZp/B +pV+ey/roGY/HbbV9dZ0NIRoPZtWFjQvMOx+RhrCLmUvMyk4hv43IbLkMm3Z0sioZ +AktZWPns5yZfAYZYeVBFYbDvvuJHJiFlD35mzuDzOwh0p02nYi2pldn6P/1i8Rbf +tk0USJUvX/bDI5ooCEwyCw== \ No newline at end of file From 95ca3b156cf02dc0c54c0410d1be198f675b5195 Mon Sep 17 00:00:00 2001 From: Andrii Date: Wed, 20 Nov 2024 09:49:27 +0000 Subject: [PATCH 32/39] add updating packages --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b7191454..468aca764 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,6 +61,9 @@ jobs: ruby-version: 3.3.5 bundler-cache: true + - name: Update packages + run: sudo apt-get update + - name: Install system dependencies run: sudo apt-get install -y libvips42 libvips-dev imagemagick From 033ff376f83ba3f18bc2d489564f2019b69573e1 Mon Sep 17 00:00:00 2001 From: Andrii Date: Wed, 20 Nov 2024 13:41:03 +0000 Subject: [PATCH 33/39] fix --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 468aca764..276c16bdc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,8 +109,8 @@ jobs: - uses: miloserdow/capistrano-deploy@v3 with: target: development - deploy_key: ${{ secrets.DEV_DEPLOY_KEY }} - enc_rsa_key_pth: config/credentials/deploy_id_ed25519_enc + deploy_key: ${{ secrets.STAGING_KEY_PASSWORD }} + enc_rsa_key_pth: config/credentials/staging_deploy_id_ed25519_enc deploy-to-production: needs: rspec From 0a64360fbadb581ad022c6aae11a98bb7dcc4066 Mon Sep 17 00:00:00 2001 From: Ivan Marynych <49816584+loqimean@users.noreply.github.com> Date: Wed, 20 Nov 2024 18:14:19 +0200 Subject: [PATCH 34/39] Update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 276c16bdc..05b3114a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -108,7 +108,7 @@ jobs: - uses: miloserdow/capistrano-deploy@v3 with: - target: development + target: staging deploy_key: ${{ secrets.STAGING_KEY_PASSWORD }} enc_rsa_key_pth: config/credentials/staging_deploy_id_ed25519_enc From a456168c87fbd39aa155c3ca777a9ecdac909878 Mon Sep 17 00:00:00 2001 From: Ivan Marynych <49816584+loqimean@users.noreply.github.com> Date: Wed, 20 Nov 2024 18:18:06 +0200 Subject: [PATCH 35/39] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa313567f..12a31fb2a 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ The latest version from the release branch 'master' is automatically deployed to ## Required to install -- Ruby 3.2.2 +- Ruby 3.3.5 - Ruby on Rails 7.1.2 - PostgreSQL 12 - Puma as a web server From 62a0d0f50759c5f1c822447ce12270b04b184c4d Mon Sep 17 00:00:00 2001 From: Ivan Marynych <49816584+loqimean@users.noreply.github.com> Date: Wed, 20 Nov 2024 18:32:21 +0200 Subject: [PATCH 36/39] Revert "Update README.md" --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 12a31fb2a..aa313567f 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ The latest version from the release branch 'master' is automatically deployed to ## Required to install -- Ruby 3.3.5 +- Ruby 3.2.2 - Ruby on Rails 7.1.2 - PostgreSQL 12 - Puma as a web server From 3cb4be9caecafbd52465b23b9dccf3ad138a0bb0 Mon Sep 17 00:00:00 2001 From: Ivan Marynych <49816584+loqimean@users.noreply.github.com> Date: Wed, 20 Nov 2024 18:33:55 +0200 Subject: [PATCH 37/39] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d1bb70b38..0f6a65d4f 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ The latest version from the release branch 'master' is automatically deployed to - Bootstrap ## Clone - + $ `git clone https://github.com/ita-social-projects/ZeroWaste.git` ## Local setup From 083b1cb327ec25aedb45b1fafa7cff6c60de874f Mon Sep 17 00:00:00 2001 From: Oleg Ivaniuk <34096259+Ivaniuk@users.noreply.github.com> Date: Fri, 22 Nov 2024 17:53:58 +0200 Subject: [PATCH 38/39] Update README.md Changed staging server link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa313567f..4254382cf 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ In order to attract attention to financial and ecological consequences of dispos ## Deployed Apps and Environments -The latest version from the 'develop' branch is automatically deployed to stage environment in Render, [staging link](https://zero-waste-staging.onrender.com/). +The latest version from the 'develop' branch is automatically deployed to stage environment in Render, [staging link](http://51.44.25.104/en). The latest version from the release branch 'master' is automatically deployed to Production environment, [production link](http://calc.zerowastelviv.org.ua/). ## Required to install From a6b220878a482514022cc5ba7fe2e72b1e5755e2 Mon Sep 17 00:00:00 2001 From: SleekMutt <84468904+SleekMutt@users.noreply.github.com> Date: Sun, 24 Nov 2024 12:46:54 +0200 Subject: [PATCH 39/39] Added flipper, so admin could enable or disable mhc calculator (#969) * Added flipper, so admin could enable or disable mhc calculator --- app/controllers/calculators_controller.rb | 7 +++++++ app/views/account/shared/_navigation.html.erb | 5 +++++ config/initializers/flipper/features.rb | 3 +++ config/locales/en/en.yml | 3 +++ config/locales/uk/uk.yml | 3 +++ spec/requests/calculators_spec.rb | 21 ++++++++++++++----- spec/support/shared/context/feature_flags.rb | 14 +++++++++++++ 7 files changed, 51 insertions(+), 5 deletions(-) diff --git a/app/controllers/calculators_controller.rb b/app/controllers/calculators_controller.rb index e0b7a4460..6693a80ad 100644 --- a/app/controllers/calculators_controller.rb +++ b/app/controllers/calculators_controller.rb @@ -2,6 +2,7 @@ class CalculatorsController < ApplicationController before_action :authenticate_user!, only: :receive_recomendations + before_action :check_mhc_flipper, only: :mhc_calculator def index if Flipper[:show_calculators_list].enabled? @@ -52,4 +53,10 @@ def collection def resource collection.friendly.find(params[:slug]) end + + def check_mhc_flipper + return if Flipper[:mhc_calculator_status].enabled? + + raise ActionController::RoutingError, "Mhc calculator flipper is disabled" + end end diff --git a/app/views/account/shared/_navigation.html.erb b/app/views/account/shared/_navigation.html.erb index 1df545e99..264fd2e55 100644 --- a/app/views/account/shared/_navigation.html.erb +++ b/app/views/account/shared/_navigation.html.erb @@ -10,6 +10,11 @@ <%= link_to t("layouts.navigation.calculate"), calculator_path, class: "tab-main-page" %> <% end %> +
  • + <% if Flipper[:mhc_calculator_status].enabled? %> + <%= link_to t("layouts.navigation.mhc_calculator"), mhc_calculator_path, class: "tab-main-page" %> + <% end %> +
  • <%= link_to t("layouts.navigation.about_us"), about_path, class: "tab-main-page" %>
  • diff --git a/config/initializers/flipper/features.rb b/config/initializers/flipper/features.rb index 24f9571ad..93387a7eb 100644 --- a/config/initializers/flipper/features.rb +++ b/config/initializers/flipper/features.rb @@ -9,4 +9,7 @@ Flipper[:sandbox_mode].en_description = "This feature flag is responsible for enabling sandbox mode" Flipper[:sandbox_mode].uk_description = "Відкриває можливість використовувати режим пісочниці" + + Flipper[:mhc_calculator_status].en_description = "This feature flag is responsible for enabling mhc calculator" + Flipper[:mhc_calculator_status].uk_description = "Відкриває можливість використовувати калькулятор продуктів жіночої гігієни" end diff --git a/config/locales/en/en.yml b/config/locales/en/en.yml index 4a624b943..3bb164b80 100644 --- a/config/locales/en/en.yml +++ b/config/locales/en/en.yml @@ -126,6 +126,7 @@ en: about_us: "About us" admin: "Admin" calculate: "Diaper calculator" + mhc_calculator: "Pad calculator" log_out: "Log Out" sign_up: "Sign Up" log_in: "Log In" @@ -480,6 +481,8 @@ en: name: "Show calculators on the public side" sandbox_mode: name: "Enable sandbox mode" + mhc_calculator_status: + name: "Enable mhc calculator" sessions: new: log_in_header: "Log In" diff --git a/config/locales/uk/uk.yml b/config/locales/uk/uk.yml index 0b52f53aa..8ba07e394 100644 --- a/config/locales/uk/uk.yml +++ b/config/locales/uk/uk.yml @@ -109,6 +109,7 @@ uk: admin: "Адміністратор" calculate: "Калькулятор підгузків" log_out: "Вийти" + mhc_calculator: "Калькулятор прокладок" sign_up: "Зареєструватися" log_in: "Увійти" contact_us: "Зв'яжіться з нами" @@ -440,6 +441,8 @@ uk: name: "Показати калькулятори користувачам" sandbox_mode: name: "Увімкнути режим пісочниці" + mhc_calculator_status: + name: "Увімкнути калькулятор продуктів жіночої гігієни" sessions: new: log_in_header: "Увійти" diff --git a/spec/requests/calculators_spec.rb b/spec/requests/calculators_spec.rb index 2be0577e0..df316e5ba 100644 --- a/spec/requests/calculators_spec.rb +++ b/spec/requests/calculators_spec.rb @@ -111,12 +111,23 @@ end describe "GET /mhc_calculator" do - it "renders pad calculator" do - get mhc_calculator_path + context "mhc calculator is enabled" do + include_context :mhc_calculator_enabled - expect(response).to be_successful - expect(response).to render_template(:mhc_calculator) - expect(response.body).to include("results") + it "renders pad calculator" do + get mhc_calculator_path + + expect(response).to be_successful + expect(response).to render_template(:mhc_calculator) + end + end + + context "mhc calculator is disabled" do + include_context :mhc_calculator_disabled + + it "renders pad calculator" do + expect { get mhc_calculator_path }.to raise_error(ActionController::RoutingError) + end end end diff --git a/spec/support/shared/context/feature_flags.rb b/spec/support/shared/context/feature_flags.rb index a037cdaa9..cb62bed93 100644 --- a/spec/support/shared/context/feature_flags.rb +++ b/spec/support/shared/context/feature_flags.rb @@ -35,5 +35,19 @@ def sandbox_mode_context(mode) end end +RSpec.shared_context :mhc_calculator_enabled do + before do + FeatureFlag.find_or_create_by!(name: "show_calculators_list") + Flipper.enable(:mhc_calculator_status) + end +end + +RSpec.shared_context :mhc_calculator_disabled do + before do + FeatureFlag.find_or_create_by!(name: "show_calculators_list") + Flipper.disable(:mhc_calculator_status) + end +end + sandbox_mode_context(:enable) sandbox_mode_context(:disable)