From 50563568592a90d2a9cd167c47f41447e37fe4d2 Mon Sep 17 00:00:00 2001 From: Olena Niemova Date: Thu, 17 Aug 2023 23:46:51 +0300 Subject: [PATCH 1/2] Tasks 1-3 --- app/controllers/application_controller.rb | 5 +--- app/models/user.rb | 2 ++ app/models/user_login.rb | 7 +++++ app/views/members/dashboard.html.erb | 10 ++++--- app/views/shop/product.html.erb | 8 +++++ db/migrate/20230817061119_add_user_logins.rb | 31 ++++++++++++++++++++ db/schema.rb | 9 ++++-- 7 files changed, 62 insertions(+), 10 deletions(-) create mode 100644 app/models/user_login.rb create mode 100644 db/migrate/20230817061119_add_user_logins.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 4c59b7b..e038a6d 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -5,10 +5,7 @@ class ApplicationController < ActionController::Base helper_method :signed_in?, :current_user def sign_in!(user) - user.transaction do - user.last_login_at = Time.now - user.save! - end + UserLogin.create(user: user, login_at: Time.now) session[:user_id] = user.id end diff --git a/app/models/user.rb b/app/models/user.rb index 8108fe7..c218366 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -9,6 +9,8 @@ class User < ApplicationRecord validates :password, presence: true has_secure_password + has_many :user_logins + def password @password ||= Password.new(password_digest) end diff --git a/app/models/user_login.rb b/app/models/user_login.rb new file mode 100644 index 0000000..55edf18 --- /dev/null +++ b/app/models/user_login.rb @@ -0,0 +1,7 @@ +class UserLogin < ApplicationRecord + belongs_to :user + + validates_presence_of :user_id, :login_at + + scope :ordered, -> { order(login_at: :desc) } +end diff --git a/app/views/members/dashboard.html.erb b/app/views/members/dashboard.html.erb index a3e2e49..8b76930 100644 --- a/app/views/members/dashboard.html.erb +++ b/app/views/members/dashboard.html.erb @@ -7,9 +7,9 @@

Profile Details:

@@ -17,7 +17,9 @@

Login History:

diff --git a/app/views/shop/product.html.erb b/app/views/shop/product.html.erb index ce21a98..3f49c2d 100644 --- a/app/views/shop/product.html.erb +++ b/app/views/shop/product.html.erb @@ -19,6 +19,14 @@ class="mb-2 text-xl font-medium leading-tight text-neutral-800 "> <%= @product.name %> +
+ <% @product.categories.each do |category| %> +

+ <%= category.name %> +

+ <% end %> +

<%= number_to_currency(@product.price_usd) %>

diff --git a/db/migrate/20230817061119_add_user_logins.rb b/db/migrate/20230817061119_add_user_logins.rb new file mode 100644 index 0000000..5477fa7 --- /dev/null +++ b/db/migrate/20230817061119_add_user_logins.rb @@ -0,0 +1,31 @@ +class AddUserLogins < ActiveRecord::Migration[7.0] + def up + create_table :user_logins do |t| + t.references :user + t.datetime :login_at, null: false + end + + execute <<-SQL + INSERT INTO user_logins (user_id, login_at) + SELECT id, last_login_at FROM users; + SQL + + remove_column :users, :last_login_at, :datetime + end + + def down + add_column :users, :last_login_at, :datetime + + execute <<-SQL + UPDATE users + SET last_login_at = ( + SELECT login_at FROM user_logins + WHERE user_logins.user_id = users.id + ORDER BY login_at DESC + LIMIT 1 + ); + SQL + + drop_table :user_logins + end +end diff --git a/db/schema.rb b/db/schema.rb index 2ac62e9..3673096 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_08_14_011401) do +ActiveRecord::Schema[7.0].define(version: 2023_08_17_061119) do create_table "categories", force: :cascade do |t| t.string "name", null: false t.string "slug", null: false @@ -38,12 +38,17 @@ t.index ["slug"], name: "index_products_on_slug" end + create_table "user_logins", force: :cascade do |t| + t.integer "user_id" + t.datetime "login_at", null: false + t.index ["user_id"], name: "index_user_logins_on_user_id" + end + create_table "users", force: :cascade do |t| t.string "email", null: false t.string "password_digest", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.datetime "last_login_at" end add_foreign_key "product_categories", "categories", on_delete: :cascade From 1fa2049d6d7100f0c03081678bcd2fad7be97aac Mon Sep 17 00:00:00 2001 From: Olena Niemova Date: Fri, 18 Aug 2023 21:36:24 +0300 Subject: [PATCH 2/2] Tasks 4, 5 --- app/assets/images/icons/heart.svg | 3 +++ app/assets/images/icons/heart_full.svg | 3 +++ app/controllers/application_controller.rb | 4 +++ .../favorite_products_controller.rb | 25 +++++++++++++++++++ app/controllers/shop_controller.rb | 7 +++--- app/models/favorite_product.rb | 7 ++++++ app/models/product.rb | 2 +- app/models/user.rb | 3 ++- app/views/members/dashboard.html.erb | 11 +++++++- app/views/shared/_favorite_button.html.erb | 13 ++++++++++ app/views/shared/_product_small_card.html.erb | 15 +++++++++++ app/views/shop/category.html.erb | 16 +++++++----- app/views/shop/product.html.erb | 13 ++++++---- config/routes.rb | 7 ++++-- db/migrate/20230817061119_add_user_logins.rb | 4 +-- .../20230818074506_add_favorite_products.rb | 10 ++++++++ db/schema.rb | 11 +++++++- 17 files changed, 131 insertions(+), 23 deletions(-) create mode 100644 app/assets/images/icons/heart.svg create mode 100644 app/assets/images/icons/heart_full.svg create mode 100644 app/controllers/favorite_products_controller.rb create mode 100644 app/models/favorite_product.rb create mode 100644 app/views/shared/_favorite_button.html.erb create mode 100644 app/views/shared/_product_small_card.html.erb create mode 100644 db/migrate/20230818074506_add_favorite_products.rb diff --git a/app/assets/images/icons/heart.svg b/app/assets/images/icons/heart.svg new file mode 100644 index 0000000..09f9e8b --- /dev/null +++ b/app/assets/images/icons/heart.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/assets/images/icons/heart_full.svg b/app/assets/images/icons/heart_full.svg new file mode 100644 index 0000000..d956025 --- /dev/null +++ b/app/assets/images/icons/heart_full.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index e038a6d..51c351a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -23,4 +23,8 @@ def authenticate_user! return redirect_to homepage_path(redirect_to: request.original_url) end end + + def render_not_found + render file: Rails.public_path.join('404.html'), status: :not_found, layout: false + end end diff --git a/app/controllers/favorite_products_controller.rb b/app/controllers/favorite_products_controller.rb new file mode 100644 index 0000000..331b769 --- /dev/null +++ b/app/controllers/favorite_products_controller.rb @@ -0,0 +1,25 @@ +class FavoriteProductsController < ApplicationController + before_action :set_product + + def create + if FavoriteProduct.create(product: @product, user: current_user) + flash[:success] = 'Product has been favorited' + else + flash[:error] = 'Something went wrong' + end + + redirect_back(fallback_location: homepage_path) + end + + def destroy + FavoriteProduct.where(product_id: @product.id, user_id: current_user.id).first.destroy + flash[:success] = 'Product has been deleted from favorites' + redirect_back(fallback_location: homepage_path) + end + + private + + def set_product + @product = Product.active.find(params[:product_id] || params[:id]) + end +end diff --git a/app/controllers/shop_controller.rb b/app/controllers/shop_controller.rb index c2a69b5..489f459 100644 --- a/app/controllers/shop_controller.rb +++ b/app/controllers/shop_controller.rb @@ -1,6 +1,4 @@ - class ShopController < ApplicationController - def category category_slug = params.require([:category_slug]) @category = Category.find_by!(slug: category_slug) @@ -9,7 +7,8 @@ def category def product category_slug, product_slug = params.require([:category_slug, :product_slug]) @category = Category.find_by!(slug: category_slug) - @product = @category.products.find_by!(slug: product_slug) - end + @product = @category.products.find_by(slug: product_slug) + render_not_found unless @product&.is_active? + end end diff --git a/app/models/favorite_product.rb b/app/models/favorite_product.rb new file mode 100644 index 0000000..bad8a5f --- /dev/null +++ b/app/models/favorite_product.rb @@ -0,0 +1,7 @@ +class FavoriteProduct < ApplicationRecord + validates_presence_of :product_id, :user_id + validates_uniqueness_of :product_id, scope: :user_id + + belongs_to :user + belongs_to :product +end diff --git a/app/models/product.rb b/app/models/product.rb index eb37ec8..3b57d09 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -2,7 +2,7 @@ class Product < ApplicationRecord validates_presence_of :name, :slug, :price_usd, :image_url validates_uniqueness_of :slug - + has_many :product_categories has_many :categories, through: :product_categories diff --git a/app/models/user.rb b/app/models/user.rb index c218366..e5edb1d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -10,6 +10,8 @@ class User < ApplicationRecord has_secure_password has_many :user_logins + has_many :favorite_products + has_many :favorites, through: :favorite_products, source: :product def password @password ||= Password.new(password_digest) @@ -19,5 +21,4 @@ def password=(new_password) @password = Password.create(new_password) self.password_digest = @password end - end diff --git a/app/views/members/dashboard.html.erb b/app/views/members/dashboard.html.erb index 8b76930..a9ac0c5 100644 --- a/app/views/members/dashboard.html.erb +++ b/app/views/members/dashboard.html.erb @@ -1,4 +1,3 @@ -

Member Dashboard

@@ -24,4 +23,14 @@
+ + <% current_user.favorites.active.any? do %> +
+
+

Favorite products:

+ + <%= render partial: "shared/product_small_card", collection: current_user.favorites.active, as: :product %> +
+
+ <% end %> diff --git a/app/views/shared/_favorite_button.html.erb b/app/views/shared/_favorite_button.html.erb new file mode 100644 index 0000000..c8cbcc8 --- /dev/null +++ b/app/views/shared/_favorite_button.html.erb @@ -0,0 +1,13 @@ +
+ <% if signed_in? %> + <% if current_user.favorite_products.exists?(product_id: product.id) %> + <%= button_to favorite_product_path(product), method: :delete do %> + <%= image_tag "icons/heart_full.svg", class: "w-full", alt: "heart icon" %> + <% end %> + <% else %> + <%= button_to favorite_products_path(product_id: product) do %> + <%= image_tag "icons/heart.svg", class: "w-full", alt: "heart icon" %> + <% end %> + <% end %> + <% end %> +
diff --git a/app/views/shared/_product_small_card.html.erb b/app/views/shared/_product_small_card.html.erb new file mode 100644 index 0000000..fd7a32b --- /dev/null +++ b/app/views/shared/_product_small_card.html.erb @@ -0,0 +1,15 @@ +
+ Photo of <%= product.name %> +
+

+ + <%= product.name %> + +

+
+ <%= render "shared/favorite_button", product: product %> +
+
+
diff --git a/app/views/shop/category.html.erb b/app/views/shop/category.html.erb index 26e860d..121c0a6 100644 --- a/app/views/shop/category.html.erb +++ b/app/views/shop/category.html.erb @@ -6,9 +6,9 @@ <%= @category.name %>
- <% @category.products.each do |product| %> + <% @category.products.active.each do |product| %>
+ class="block w-1/2 my-4 px-3 pt-3 rounded-lg bg-white shadow-[0_2px_15px_-3px_rgba(0,0,0,0.07),0_10px_20px_-2px_rgba(0,0,0,0.04)]">
-
- <%= product.name %> -
+
+
+ <%= product.name %> +
+ <%= render "shared/favorite_button", product: product %> +
+

<%= product.description %>

diff --git a/app/views/shop/product.html.erb b/app/views/shop/product.html.erb index 3f49c2d..5e8a49b 100644 --- a/app/views/shop/product.html.erb +++ b/app/views/shop/product.html.erb @@ -9,16 +9,19 @@
+ class="block w-full mx-auto my-4 px-3 rounded-lg bg-white shadow-[0_2px_15px_-3px_rgba(0,0,0,0.07),0_10px_20px_-2px_rgba(0,0,0,0.04)]"> Photo of <%= @product.name %>
-
- <%= @product.name %> -
+
+
+ <%= @product.name %> +
+ <%= render "shared/favorite_button", product: @product %> +
<% @product.categories.each do |category| %>