Commit efca5cdb by Trong Huu Nguyen

Merge branch 'dhp_stock' into 'development'

[Review] Stock feature

See merge request !4
parents f0d57a97 dfea8b87
......@@ -52,6 +52,12 @@ gem 'font-awesome-rails', '~> 4.7', '>= 4.7.0.2'
gem 'rails-assets-bootstrap-touchspin', source: 'https://rails-assets.org'
# Simple, efficient background processing for Ruby.
gem 'sidekiq', '~> 5.0', '>= 5.0.2'
# Toastr: Simple javascript toast notifications, plugged into the rails asset pipeline.
gem 'toastr-rails', '~> 1.0', '>= 1.0.3'
# Helpful method when you need to add some logic that figures out if the link
# (or more often navigation item)
# is selected based on the current page or other arbitrary condition
gem 'active_link_to', '~> 1.0', '>= 1.0.4'
# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development
......
......@@ -25,6 +25,9 @@ GEM
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
active_link_to (1.0.4)
actionpack
addressable
activejob (5.1.1)
activesupport (= 5.1.1)
globalid (>= 0.3.6)
......@@ -225,6 +228,8 @@ GEM
thor (0.19.4)
thread_safe (0.3.6)
tilt (2.0.7)
toastr-rails (1.0.3)
railties (>= 3.1.0)
turbolinks (5.0.1)
turbolinks-source (~> 5)
turbolinks-source (5.0.3)
......@@ -251,6 +256,7 @@ PLATFORMS
ruby
DEPENDENCIES
active_link_to (~> 1.0, >= 1.0.4)
bootstrap-sass (~> 3.3.6)
byebug
capybara (~> 2.13)
......@@ -275,6 +281,7 @@ DEPENDENCIES
sidekiq (~> 5.0, >= 5.0.2)
spring
spring-watcher-listen (~> 2.0.0)
toastr-rails (~> 1.0, >= 1.0.3)
turbolinks (~> 5)
tzinfo-data
uglifier (>= 1.3.0)
......
......@@ -12,7 +12,6 @@
//
//= require jquery
//= require jquery_ujs
//= require bootstrap-touchspin
//# require rails-ujs
//= require turbolinks
//# require_tree .
(function($) {
toastr.options.closeButton = true;
}).apply(this, [jQuery]);
\ No newline at end of file
//= require cart/cart
\ No newline at end of file
//= require toastr
//= require bootstrap-touchspin
//= require cart/cart
//= require global
\ No newline at end of file
......@@ -12,8 +12,5 @@
*
*# require_tree .
*= require_self
*= require bootstrap-touchspin
*= require custom
*= require main_style
*= require skin_14
*/
// "bootstrap-sprockets" must be imported before "bootstrap" and "bootstrap/variables"
@import "bootstrap-sprockets";
@import "bootstrap";
@import "font-awesome";
\ No newline at end of file
@import "font-awesome";
@import "bootstrap-touchspin";
@import "toastr";
@import "main_style";
@import "skin_14";
\ No newline at end of file
......@@ -87,7 +87,14 @@ a {
padding-right: 12px;
}
}
/*
* Notification
*/
.alert-notice {
color: #3c763d;
background-color: #dff0d8;
border-color: #d6e9c6;
}
/*
* Header
*/
......@@ -183,7 +190,33 @@ a {
font-size: 14px;
font-weight: 600;
}
#header .custom-block {
display: inline-block;
text-align: center;
font-size: 14px;
line-height: 39px;
font-weight: 400;
vertical-align: middle;
}
@media (max-width: 991px) {
#header .custom-block {
display: none;
}
}
#header .custom-block i {
margin-right: 5px;
}
#header .custom-block .split {
display: inline-block;
height: 14px;
width: 1px;
border-right: 1px solid #ccc;
vertical-align: middle;
margin: 0 10px;
}
#header .custom-block a {
font-size: 12px;
}
/*
* Button
*/
......@@ -947,6 +980,60 @@ html .featured-box-primary .box-content {
/*
* Product details
*/
@media (max-width: 767px) {
.product-img-box {
margin-bottom: 25px;
}
}
.product-img-box img {
display: block;
width: 100%;
height: auto;
}
.product-img-box .product-img-wrapper {
padding: 3px;
border: 1px solid #ddd;
border-radius: 0;
}
.product-img-box-wrapper {
position: relative;
margin-bottom: 10px;
}
.product-details-box .product-name {
margin: 15px 0;
font-size: 28px;
font-weight: 600;
line-height: 1;
color: #555;
}
.product-details-box .product-short-desc {
padding: 0 0 10px;
border-bottom: 1px solid #ebebeb;
}
.product-details-box .product-short-desc p {
font-size: 14px;
line-height: 1.65;
margin: 0 0 20px;
}
.product-details-box .product-detail-info {
padding-bottom: 20px;
margin-top: 20px;
border-bottom: 1px solid #ebebeb;
}
.product-details-box .product-detail-info .product-price-box {
margin: 0 0 20px;
}
.product-details-box .product-detail-info .product-price-box .product-price {
font-size: 33px;
line-height: 1;
color: #000;
}
.product-details-box .product-detail-info .availability {
margin: 0 0 10px;
font-size: 14px;
color: #777;
font-weight: 400;
}
.product-details-box .product-detail-qty {
display: inline-block;
vertical-align: middle;
......@@ -1276,4 +1363,154 @@ html .featured-box-primary .box-content {
.form-col .checkout-review-action h5 span {
margin-left: 45px;
}
/*
* Panel box
*/
.panel-box {
margin-bottom: 30px;
}
.panel-box a {
color: #000;
}
.panel-box a:hover, .panel-box a:focus {
color: #0f0f0f;
}
.panel-box .panel-box-title {
padding: 10px 15px;
border: 1px solid #ddd;
background-color: #f5f5f5;
position: relative;
border-radius: 0 0 0 0;
}
.panel-box .panel-box-title:after {
content: '';
display: table;
clear: both;
}
.panel-box .panel-box-title h3, .panel-box .panel-box-title h4 {
float: left;
font-size: 13px;
font-weight: 700;
line-height: 18px;
text-transform: uppercase;
margin: 0;
color: #313131;
}
.panel-box .panel-box-title .panel-box-edit {
float: right;
line-height: 18px;
font-size: 12px;
}
.panel-box .panel-box-content {
padding: 20px 15px;
font-size: 13px;
border: 1px solid #ddd;
border-top: 0;
border-radius: 0 0 0 0;
background-color: #fbfbfb;
min-height: 135px;
}
/*
* Sidebar
*/
aside ul.nav-list > li > a {
font-size: 13px;
padding-left: 21px;
}
aside ul.nav-list > li > a:before {
margin-left: -11px;
}
aside ul.nav-list > li.active > a {
color: #000;
font-weight: 600;
}
aside ul.nav-list > li.active > a:before {
border-left-color: #000;
}
.sidebar h4 {
margin: 5px 0 10px;
line-height: 1.2;
font-size: 16px;
font-weight: bold;
color: #313131;
}
.sidebar .nav.nav-list {
margin-bottom: 30px;
}
/* Nav List */
ul.nav-list li {
margin: 0;
padding: 0;
display: block;
position: relative;
}
ul.nav-list li a {
-webkit-transition: background 0.1s;
-moz-transition: background 0.1s;
transition: background 0.1s;
border-bottom: 1px solid #EDEDDE;
padding: 8px 0 8px 18px;
display: block;
color: #666;
font-size: 0.9em;
text-decoration: none;
line-height: 20px;
}
ul.nav-list li a:before {
content: "";
display: inline-block;
width: 0;
height: 0;
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
border-left: 4px solid #333;
margin: 7px 0 0 -9px;
position: absolute;
}
ul.nav-list li a:hover {
background-color: #eee;
text-decoration: none;
}
ul.nav-list li.active > a {
font-weight: bold;
}
ul.nav-list li ul {
list-style: none;
margin: 0 0 0 25px;
padding: 0;
}
ul.nav-list.hide-not-active li ul {
display: none;
}
ul.nav-list.hide-not-active li.active ul {
display: block;
}
ul.nav-list.show-bg-active .active > a {
background-color: #f5f5f5;
}
ul.nav-list.show-bg-active .active > a:hover {
background-color: #eee;
}
ul.narrow li a {
padding: 4px 0;
}
ul.nav-pills > li.active > a {
background-color: #CCC;
}
ul.nav-pills > li.active > a:hover, ul.nav-pills > li.active > a:focus {
background-color: #CCC;
}
\ No newline at end of file
......@@ -69,61 +69,15 @@ html .btn-primary:active:focus {
.btn {
border-radius: 0;
}
.font-weight-semibold {
font-weight: 600 !important;
}
.featured-box .box-content {
border-radius: 0;
}
.product-essential {
margin-bottom: 50px;
}
@media (max-width: 767px) {
.product-img-box {
margin-bottom: 25px;
}
}
.product-img-box img {
display: block;
width: 100%;
height: auto;
}
.product-img-box .product-img-wrapper {
padding: 3px;
border: 1px solid #ddd;
border-radius: 0;
}
.product-img-box-wrapper {
position: relative;
margin-bottom: 10px;
}
.product-details-box .product-name {
margin: 15px 0;
font-size: 28px;
font-weight: 600;
line-height: 1;
color: #555;
}
.product-details-box .product-short-desc {
padding: 0 0 10px;
border-bottom: 1px solid #ebebeb;
}
.product-details-box .product-short-desc p {
font-size: 14px;
line-height: 1.65;
margin: 0 0 20px;
}
.product-details-box .product-detail-info {
padding-bottom: 20px;
margin-top: 20px;
border-bottom: 1px solid #ebebeb;
}
.product-details-box .product-detail-info .product-price-box {
margin: 0 0 20px;
}
.product-details-box .product-detail-info .product-price-box .product-price {
font-size: 33px;
line-height: 1;
color: #000;
}
#header .header-body {
border-top: none;
background-color: transparent;
......
class CartsController < ApplicationController
include CartsHelper
include LineItemsHelper
before_action :set_cart
before_action :get_line_items
before_action :set_product_item,
only: [:add_product_item, :update_product_item, :remove_product_item]
before_action :build_empty_quantity_notice, if: :quantity_equal_zero?,
only: :add_product_item
rescue_from ActiveRecord::RecordNotFound, with: :invalid_cart
# GET /cart
def index
@product_items = @cart.product_items
end
# POST /cart/add/1
def add_product_item
if @cart.add_product_item(params[:product_id], params[:product_quantity])
respond_to do |format|
format.js do
@notice = { msg: "#{@product_item.title} was sucessfully added to your cart",
type: :success }
end
end
else
render_warning_notice('Product is out of stock')
end
end
# PUT/PATCH /cart/update/1
def update_product_item
if @cart.update_product_item(params[:product_id],
params[:product_quantity])
redirect_to cart_index_url, notice: "#{@product_item.title} was sucessfully updated"
else
redirect_to cart_index_url, notice: "Can not update #{@product_item.title}"
end
end
# DELETE /cart/1
def remove_product_item
if @cart.remove_product_item(params[:product_id])
redirect_to cart_index_url, notice: "#{@product_item.title} was sucessfully removed from your cart"
else
redirect_to cart_index_url, notice: "#{@product_item.title} can not remove from your cart"
end
end
# DELETE /cart/remove
def destroy
destroy_cart_session
respond_to do |format|
format.html { redirect_to root_url }
end
redirect_to root_url, notice: 'Your cart is empty'
end
private
def invalid_cart
destroy_cart_session
redirect_to root_url
end
def set_product_item
@product_item = Product.find(params[:product_id])
end
def quantity_equal_zero?
params[:product_quantity].to_i.zero?
end
def build_empty_quantity_notice
render_warning_notice('Please specify quantity')
end
def render_warning_notice(notice)
respond_to do |format|
format.html { redirect_to cart_index_url, notice: notice }
format.js { render 'warning_notice', locals: { notice: notice } }
end
end
end
\ No newline at end of file
class LineItemsController < ApplicationController
include CartsHelper
before_action :set_cart, only: [:create, :update, :destroy]
# We don not render line item details page
def show
redirect_to product_url(params[:id])
end
# POST /line_items
def create
product = Product.find(params[:product_id])
add_to_cart(product.id.to_s, params[:product_quantity].to_i)
respond_to do |format|
format.html { redirect_to cart_index_url, notice: "#{product.title} was sucessfully added to your cart" }
format.js
end
end
# PATCH/PUT /line_items/1
def update
update_cart_item(*update_params.to_h.values)
respond_to do |format|
format.html { redirect_to cart_index_url }
end
end
# DELETE /line_items/1
def destroy
remove_line_item_from_cart(params[:id])
product = Product.find(params[:id])
respond_to do |format|
format.html { redirect_to cart_index_url, notice: "#{product.title} was sucessfully removed from your cart" }
end
end
private
# Never trust parameters from the scary internet, only allow the white list through.
def update_params
params.permit(:id, :qty_input)
end
end
\ No newline at end of file
class OrdersController < ApplicationController
include CartsHelper
include LineItemsHelper
before_action :authenticate_user!, only: :new
before_action :set_cart, only: [:new, :create]
before_action :get_line_items, only: :new
before_action :set_order_items, only: :new
# GET /orders/new
def new
......@@ -14,23 +12,28 @@ class OrdersController < ApplicationController
# POST /orders
def create
@order = current_user.orders.create
create_line_items_for_order(@order)
@order = Order.create(user: current_user)
save_order_items(@order)
respond_to do |format|
if @order.save
destroy_cart_session
OrderMailer.send_order_detail_to_user(current_user, @order).deliver_later
format.html { redirect_to root_url, notice: 'Your order is successfully created' }
else
format.html { redirect_to root_url, notice: 'Your order can not be created' }
end
if @order.present?
destroy_cart_session
OrderMailer.send_order_detail_to_user(current_user, @order).deliver_later
redirect_to root_url, notice: 'Your order is successfully created'
else
redirect_to root_url, notice: 'Your order can not be created'
end
end
def create_line_items_for_order(order)
@cart.each do |product_id, product_attrs|
order.line_items.create(product_id: product_id, quantity: product_attrs['quantity'])
def save_order_items(order)
@cart.product_items.values.each do |attrs|
order.product_items.create(product: attrs[:product],
quantity: attrs[:quantity])
end
end
private
def set_order_items
@order_items = @cart.product_items
end
end
\ No newline at end of file
......@@ -47,8 +47,8 @@ class ProductsController < ApplicationController
# Never trust parameters from the scary internet, only allow the white list through.
def product_params
params.require(:product).permit(:title, :sku, :price, :description,
:category_id, :image_url)
params.require(:product).permit(:title, :sku, :price, :quantity,
:description, :category_id, :image_url)
end
# Is current user own current editing product?
......
class UsersController < ApplicationController
before_action :authenticate_user!
# GET /users/1
def show
@user = current_user
end
# GET /users/products
def products
@products = current_user.products.page(params[:page]).per(5)
end
end
\ No newline at end of file
module CartsHelper
def set_cart
session[:cart] ||= {}
@cart = session[:cart]
end
def add_to_cart(product_id, quantity)
return if quantity.zero?
if @cart.key?(product_id)
@cart[product_id][:quantity.to_s] += quantity
else
@cart[product_id] = { quantity: quantity }
end
end
def update_cart_item(product_id, quantity)
return unless @cart.key?(product_id)
if quantity.to_i.zero?
remove_line_item_from_cart(product_id)
else
@cart[product_id][:quantity.to_s] = quantity.to_i
end
end
def remove_line_item_from_cart(product_id)
@cart.tap { |cart| cart.delete(product_id) } if @cart.key?(product_id)
@cart ||= Cart.new(session[:cart])
end
def destroy_cart_session
......@@ -31,14 +9,10 @@ module CartsHelper
end
def cart_total_price
@cart.sum do |product_id, product_attrs|
product = Product.find(product_id)
product.price * product_attrs[:quantity.to_s].to_i
end
@cart.cal_total_price
end
def cart_total_items
return 0 if @cart.nil?
@cart.sum { |_, product_attrs| product_attrs[:quantity.to_s] || product_attrs[:quantity] }
@cart.cal_total_items
end
end
module LineItemsHelper
def get_line_items
@line_items = {}
@cart.each do |product_id, product_attrs|
@line_items[product_id] = { product: Product.find(product_id),
quantity: product_attrs['quantity'].to_i }
end
end
end
\ No newline at end of file
class Order < ApplicationRecord
has_many :line_items, dependent: :destroy
has_many :product_items, dependent: :destroy
belongs_to :user
def total_price
line_items.to_a.sum { |item| item.product.price * item.quantity }
product_items.to_a.sum { |item| item.product.price * item.quantity }
end
end
\ No newline at end of file
......@@ -6,4 +6,12 @@ class Product < ApplicationRecord
validates :category_id, presence: true
validates :user_id, presence: true
def in_stock?(required_quantity)
quantity >= required_quantity.to_i
end
def belongs_to_user?(current_user)
current_user == user
end
end
class LineItem < ApplicationRecord
class ProductItem < ApplicationRecord
belongs_to :product, optional: true
belongs_to :order, optional: true
after_create :update_product_quantity
def total_price
product.price * quantity
end
def update_product_quantity
product.update(quantity: product.quantity - quantity)
end
end
\ No newline at end of file
class Cart
def initialize(cart_session = {})
@cart = cart_session
end
def product_items
product_items = {}
@cart.each do |id, attrs|
product_items[id] = { product: Product.find(id),
quantity: attrs[:quantity.to_s].to_i }
end
product_items
end
def add_product_item(product_id, quantity)
real_quantity = product_quantity(product_id, quantity)
return false unless product_is_in_stock?(product_id, real_quantity)
if product_item_exist?(product_id)
@cart[product_id.to_s][:quantity.to_s] += real_quantity.to_i
else
@cart[product_id.to_s] = { quantity: real_quantity.to_i }
end
end
def update_product_item(product_id, quantity)
return false unless can_update_product_item?(product_id, quantity)
if quantity.to_i.zero?
remove_product_item(product_id)
else
@cart[product_id][:quantity.to_s] = quantity.to_i
end
end
def remove_product_item(product_id)
@cart.tap { |cart| cart.delete(product_id) } if product_item_exist?(product_id)
end
def product_quantity(product_id, new_quantity = 0)
return new_quantity.to_i if @cart.empty? || !product_item_exist?(product_id)
old_quantity = @cart[product_id.to_s][:quantity] ||
@cart[product_id.to_s][:quantity.to_s]
new_quantity.to_i + old_quantity
end
def can_update_product_item?(product_id, quantity)
product_is_in_stock?(product_id, quantity) && product_item_exist?(product_id)
end
def product_is_in_stock?(product_id, quantity)
Product.find(product_id).in_stock?(quantity)
end
def product_item_exist?(product_id)
@cart.key?(product_id.to_s)
end
def cal_total_price
product_items.sum do |_product_id, attrs|
attrs[:product].price * attrs[:quantity]
end
end
def cal_total_items
return 0 if @cart.nil?
@cart.sum { |_product_id, attrs| attrs[:quantity] || attrs[:quantity.to_s] }
end
end
\ No newline at end of file
<tr>
<td colspan="6">
<%= link_to 'Continue shopping', root_url, class: 'btn btn-default hover-primary btn-continue' %>
<%= button_to 'Clear Shopping Cart', remove_cart_path, method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-default hover-primary btn-clear' %>
<%= button_to 'Clear Shopping Cart', cart_destroy_path, method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-default hover-primary btn-clear' %>
</td>
</tr>
\ No newline at end of file
......@@ -10,8 +10,8 @@
</tr>
</thead>
<tbody>
<% @line_items.each do |id, line_item| %>
<%= render partial: 'line_item', locals: { id: id, line_item: line_item } %>
<% @product_items.each do |id, line_item| %>
<%= render partial: 'product_item', locals: { id: id, product_item: line_item } %>
<% end %>
</tbody>
<tfoot>
......
<tr>
<td class="product-action-td">
<%= link_to fa_icon('times'), line_item_path(id), method: :delete, data: { confirm: 'Are you sure?' } %>
<%= link_to fa_icon('times'), cart_remove_product_item_path(id), method: :delete, data: { confirm: 'Are you sure?' } %>
</td>
<td class="product-image-td"></td>
<td class="product-name-td">
<h2 class="product-name">
<%= link_to line_item[:product].title, product_url(line_item[:product]) %>
<%= link_to product_item[:product].title, product_item_url(product_item[:product]) %>
</h2>
</td>
<td><%= number_to_currency(line_item[:product].price) %></td>
<td><%= number_to_currency(product_item[:product].price) %></td>
<td>
<%= form_tag line_item_path(id), method: :put do %>
<%= form_tag cart_update_product_item_path(id), method: :put do %>
<div class="qty-holder">
<%= number_field_tag :qty_input, line_item[:quantity], data: { product_id: id}, class: 'qty-input' %>
<%= number_field_tag :product_quantity, product_item[:quantity], id: nil, class: 'qty-input' %>
</div>
<% end %>
</td>
<td><%= number_to_currency(line_item[:product].price * line_item[:quantity]) %></td>
<td><%= number_to_currency(product_item[:product].price * product_item[:quantity]) %></td>
</tr>
\ No newline at end of file
toastr.options.closeButton = true;
toastr.options.timeOut = 1000; // How long the toast will display without user interaction
toastr.options.extendedTimeOut = 2000; // How long the toast will display after a user hovers over it
$('.cart-qty').html("<%= render 'carts/cart_quantity' %>");
<% if @notice %>
toastr.<%= @notice[:type] %>('<%= @notice[:msg] %>');
<% end %>
\ No newline at end of file
toastr.options.closeButton = true;
toastr.options.timeOut = 1000; // How long the toast will display without user interaction
toastr.options.extendedTimeOut = 2000; // How long the toast will display after a user hovers over it
<% if notice %>
toastr.warning('<%= notice %>');
<% end %>
\ No newline at end of file
......@@ -13,6 +13,30 @@
</h4>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<%= f.label :first_name %>
<%= f.text_field :first_name, class: "form-control" %>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<%= f.label :last_name %>
<%= f.text_field :last_name, class: "form-control" %>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="form-group">
<%= f.label :username %>
<%= f.text_field :username, class: "form-control" %>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="form-group">
<%= f.label :email %>
......@@ -57,5 +81,8 @@
</div>
</div>
</div>
<div class="col-md-3 col-md-pull-9">
<%= render 'shared/account_sidebar' %>
</div>
</div>
</div>
......@@ -11,6 +11,30 @@
<%= devise_error_messages! %>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<%= f.label :first_name %>
<%= f.text_field :first_name, class: "form-control" %>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<%= f.label :last_name %>
<%= f.text_field :last_name, class: "form-control" %>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="form-group">
<%= f.label :username %>
<%= f.text_field :username, class: "form-control" %>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="form-group">
<%= f.label :email %>
......
<section class="form-section">
<div class="container">
<h2 class="h2 heading-primary font-weight-normal mb-md mt-xlg">
Welcome back! Sign in to your account
Login or Create an Account
</h2>
<div class="featured-box featured-box-primary featured-box-flat featured-box-text-left mt-md">
<div class="box-content">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<div class="col-sm-6">
<div class="form-content">
<h3 class="heading-text-color font-weight-normal">New Customers</h3>
<p>By creating an account with our store, you will be able to move through the checkout process faster,
store multiple shipping addresses, view and track your orders in your account and more.</p>
</div>
<div class="form-action clea rfix">
<%= link_to 'Create an Account', new_user_registration_path, class: 'btn btn-primary' %>
</div>
</div>
<div class="col-md-6">
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<div class="form-content">
<h3 class="heading-text-color font-weight-normal">
......@@ -15,7 +25,7 @@
<div class="form-group">
<%= f.label :login %>
<%= f.email_field :login, autofocus: true, class: "form-control" %>
<%= f.text_field :login, autofocus: true, class: "form-control" %>
</div>
<div class="form-group">
......@@ -25,7 +35,7 @@
</div>
<div class="form-action clearfix">
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
<%= link_to "Lost your password?", new_password_path(resource_name), class: 'pull-left' %>
<%# link_to "Lost your password?", new_password_path(resource_name), class: 'pull-left' %>
<% end -%>
<%= f.submit "Submit", class: "btn btn-primary" %>
</div>
......
......@@ -16,6 +16,17 @@
<div class="header-column">
<div class="row">
<div class="cart-area">
<div class="custom-block">
<% if user_signed_in? %>
<%= link_to 'MY PROFILE', user_profile_path %>
<span class="split"></span>
<%= link_to 'SIGN OUT', destroy_user_session_path, method: :delete %>
<% else %>
<%= link_to 'SIGN IN', new_user_session_path %>
<span class="split"></span>
<%= link_to 'SIGN UP', new_user_registration_path %>
<% end %>
</div>
<div class="cart-dropdown">
<a href="<%= cart_index_url %>" class="cart-dropdown-icon">
<i class="minicart-icon"></i>
......
$('.cart-qty').html("<%= render 'carts/cart_quantity' %>");
\ No newline at end of file
......@@ -75,7 +75,7 @@
<th align="center" bgcolor="#EEEEEE" style="border: 1px solid #CCCCCC;">Price</th>
<th align="center" bgcolor="#EEEEEE" style="border: 1px solid #CCCCCC;">Total</th>
</tr>
<% @order.line_items.each do |item| %>
<% @order.product_items.each do |item| %>
<tr valign="top">
<td align="left" style="border: 1px solid #CCCCCC;">
<%= item.product.id %>
......
......@@ -28,8 +28,8 @@
</tr>
</thead>
<tbody>
<% @line_items.each do |id, line_item| %>
<%= render partial: 'line_items/line_item', locals: {id: id, line_item: line_item} %>
<% @order_items.each do |id, product_item| %>
<%= render partial: 'product_items/product_item', locals: {id: id, product_item: product_item} %>
<% end %>
</tbody>
</table>
......
......@@ -2,10 +2,10 @@
<td class="product-image-td"></td>
<td class="product-name-td">
<h2 class="product-name">
<%= link_to line_item[:product].title, product_url(line_item[:product]) %>
<%= link_to product_item[:product].title, product_url(product_item[:product]) %>
</h2>
</td>
<td><%= number_to_currency(line_item[:product].price) %></td>
<td><%= line_item[:quantity]%></td>
<td><%= number_to_currency(line_item[:product].price * line_item[:quantity]) %></td>
<td><%= number_to_currency(product_item[:product].price) %></td>
<td><%= product_item[:quantity]%></td>
<td><%= number_to_currency(product_item[:product].price * product_item[:quantity]) %></td>
</tr>
\ No newline at end of file
......@@ -12,14 +12,21 @@
<div class="col-md-6">
<div class="form-group">
<%= f.label :sku %>
<%= f.text_field :sku, class: "form-control", step: :any %>
<%= f.text_field :sku, class: "form-control" %>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<%= f.label :price %>
<%= f.number_field :price, class: "form-control" %>
<%= f.number_field :price, step: :any, class: "form-control" %>
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<%= f.label :quantity %>
<%= f.number_field :quantity, class: "form-control" %>
</div>
</div>
......
<li class="product-<%= product.id %>">
<div class="product product-list">
<figure class="product-image-area">
<%= link_to image_tag(get_product_thumbnail(product, 170, 204)), product_path(product) %>
</figure>
<div class="product-details-area">
<h2 class="product-name">
<%= link_to product.title, product_url(product) %>
</h2>
<div class="product-short-desc">
<%= product.description %>
<div class="product-view">
<div class="product-essential">
<div class="row">
<div class="product-img-box col-sm-5">
<div class="product-img-box-wrapper">
<div class="product-img-wrapper">
<%= image_tag(get_product_thumbnail(product, 715, 952)) %>
</div>
</div>
</div>
<div class="product-price-box">
<span class="product-price"><%= number_to_currency(product.price) %></span>
<div class="product-details-box col-sm-7">
<h1 class="product-name">
<%= product.title %>
<%= link_to fa_icon('pencil'), edit_product_path if product.belongs_to_user?(current_user) %>
</h1>
<div class="product-short-desc">
<%= product.description %>
</div>
<div class="product-detail-info">
<div class="product-price-box">
<span class="product-price"><%= number_to_currency(product.price) %></span>
</div>
<p class="availability">
<span class="font-weight-semibold">Availability:</span>
<%= product.quantity %>
</p>
</div>
<div class="product-actions">
<%= form_tag cart_add_product_item_path(product.id), remote: true do %>
<div class="product-detail-qty">
<%= number_field_tag :product_quantity, 1, min: 0, class: 'vertical-spinner' %>
</div>
<%= submit_tag 'Add to cart', class: 'addtocart' %>
<% end %>
</div>
</div>
</div>
</div>
</li>
</div>
\ No newline at end of file
<% @recommended_products.each do |product| %>
<div class="products-grid columns<%= columns %>">
<% products.each do |product| %>
<li class="product-<%= product.id %>">
<div class="product">
<figure class="product-image-area">
......@@ -14,4 +15,5 @@
</div>
</div>
</li>
<% end %>
\ No newline at end of file
<% end %>
</div>
\ No newline at end of file
<ul class="products-list">
<% products.each do |product| %>
<li class="product-<%= product.id %>">
<div class="product product-list">
<figure class="product-image-area">
<%= link_to image_tag(get_product_thumbnail(product, 170, 204)), product_path(product) %>
</figure>
<div class="product-details-area">
<h2 class="product-name">
<%= link_to product.title, product_url(product) %>
<%= link_to fa_icon('pencil'), edit_product_path(product) if product.belongs_to_user?(current_user) %>
</h2>
<div class="product-short-desc">
<%= product.description %>
</div>
<div class="product-price-box">
<span class="product-price"><%= number_to_currency(product.price) %></span>
</div>
</div>
</div>
</li>
<% end %>
</ul>
\ No newline at end of file
<div class="container">
<div class="row">
<div class="col-md-9 col-md-push-3 create-product form-section">
<div class="col-md-12 create-product form-section">
<h1 class="h2 heading-primary font-weight-normal">
Edit Product #<%= @product.id %>
(<%= link_to 'Delete', @product, method: :delete, data: { confirm: 'Are your sure?' } %>)
......@@ -8,6 +8,7 @@
<div class="featured-box featured-box-primary featured-box-flat featured-box-text-left mt-md">
<div class="box-content">
<p><%= link_to 'Back to product', product_path %></p>
<%= render 'form' %>
</div>
</div>
......
<div class="container">
<div class="product-view">
<div class="product-essential">
<div class="row">
<div class="product-img-box col-sm-5">
<div class="product-img-box-wrapper">
<div class="product-img-wrapper">
<%= image_tag(get_product_thumbnail(@product, 715, 952)) %>
</div>
</div>
</div>
<div class="product-details-box col-sm-7">
<h1 class="product-name">
<%= @product.title %>
</h1>
<div class="product-short-desc">
<%= @product.description %>
</div>
<div class="product-detail-info">
<div class="product-price-box">
<span class="product-price"><%= number_to_currency(@product.price) %></span>
</div>
</div>
<div class="product-actions">
<%= form_tag line_items_path(product_id: @product), remote: true do %>
<div class="product-detail-qty">
<%= number_field_tag :product_quantity, 1, min: 0, class: 'vertical-spinner' %>
</div>
<%= submit_tag 'Add to cart', class: 'addtocart' %>
<% end %>
</div>
</div>
</div>
</div>
</div>
<%= render @product %>
</div>
\ No newline at end of file
<aisde class="sidebar">
<h4>My Account</h4>
<ul class="nav nav-list">
<%= active_link_to 'My Dashboard', user_profile_path, wrap_tag: :li, active: :exclusive %>
<%= active_link_to 'Edit Account Information', edit_user_registration_path, wrap_tag: :li, active: :exclusive %>
<%= active_link_to 'My Products', user_products_path, wrap_tag: :li, active: :exclusive %>
</ul>
</aisde>
\ No newline at end of file
......@@ -5,16 +5,12 @@
<h2 class="h2 heading-primary mt-lg clearfix">
<span>Recommended Items</span>
</h2>
<div class="products-grid columns3">
<%= render 'products/recommended' %>
</div>
<%= render partial: 'products/product_grid', locals: { products: @recommended_products, columns: 3 } %>
<h2 class="h2 heading-primary mt-lg clearfix">
<span>Newest Items</span>
</h2>
<ul class="products-list">
<%= render @latest_products %>
</ul>
<%= render partial: 'products/product_list', locals: { products: @latest_products } %>
<div class="toolbar-bottom">
<div class="toolbar">
......
<div class="container">
<div class="row">
<div class="col-md-9 col-md-push-3 my-account">
<h2 class="h2 heading-primary font-weight-normal">
My Products
</h2>
<%= render partial: 'products/product_list', locals: { products: @products } %>
<div class="toolbar-bottom">
<div class="toolbar">
<div class="sorter">
<%= paginate @products, theme: 'bootstrap' %>
</div>
</div>
</div>
</div>
<div class="col-md-3 col-md-pull-9">
<%= render 'shared/account_sidebar' %>
</div>
</div>
</div>
\ No newline at end of file
<div class="container">
<div class="row">
<div class="col-md-9 col-md-push-3 my-account">
<h2 class="h2 heading-primary font-weight-normal">
My Dashboard
</h2>
<h2 class="h3 mb-sm">
<strong>Account Information</strong>
</h2>
<div class="row">
<div class="col-sm-12">
<div class="panel-box">
<div class="panel-box-title">
<h3>Contact Information</h3>
<%= link_to 'Edit', edit_user_registration_path, class: 'panel-box-edit' %>
</div>
<div class="panel-box-content">
<p><%= @user.first_name %> <%= @user.last_name %></p>
<p><%= @user.email %></p>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3 col-md-pull-9">
<%= render 'shared/account_sidebar' %>
</div>
</div>
</div>
\ No newline at end of file
......@@ -4,8 +4,20 @@ Rails.application.routes.draw do
resources :categories
resources :products
resources :orders, only: [:new, :create, :show]
resources :line_items
resources :cart, only: :index, controller: 'carts'
delete '/cart/remove', to: 'carts#destroy', as: 'remove_cart'
resources :product_items
scope 'cart' do
get '/', to: 'carts#index', as: 'cart_index'
post '/add/:product_id', to: 'carts#add_product_item',
as: 'cart_add_product_item'
put '/update/:product_id', to: 'carts#update_product_item',
as: 'cart_update_product_item'
delete '/remove/:product_id', to: 'carts#remove_product_item',
as: 'cart_remove_product_item'
delete '/remove', to: 'carts#destroy', as: 'cart_destroy'
end
devise_for :users
scope 'users' do
get '/', to: 'users#show', as: 'user_profile'
get '/products', to: 'users#products', as: 'user_products'
end
end
class CreateLineItems < ActiveRecord::Migration[5.1]
class CreateProductItems < ActiveRecord::Migration[5.1]
def change
create_table :line_items do |t|
create_table :product_items do |t|
t.integer :quantity, default: 1
t.references :product, index: true
t.references :order, index: true
......
class AddQuantityToProducts < ActiveRecord::Migration[5.1]
def change
add_column :products, :quantity, :integer, default: 1
end
end
......@@ -10,28 +10,28 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170613012113) do
ActiveRecord::Schema.define(version: 20170615035827) do
create_table "categories", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
t.string "title"
t.text "description"
end
create_table "line_items", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
t.integer "quantity", default: 1
t.bigint "product_id"
t.bigint "order_id"
create_table "orders", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
t.bigint "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["order_id"], name: "index_line_items_on_order_id"
t.index ["product_id"], name: "index_line_items_on_product_id"
t.index ["user_id"], name: "index_orders_on_user_id"
end
create_table "orders", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
t.bigint "user_id"
create_table "product_items", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
t.integer "quantity", default: 1
t.bigint "product_id"
t.bigint "order_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id"], name: "index_orders_on_user_id"
t.index ["order_id"], name: "index_product_items_on_order_id"
t.index ["product_id"], name: "index_product_items_on_product_id"
end
create_table "products", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
......@@ -44,6 +44,7 @@ ActiveRecord::Schema.define(version: 20170613012113) do
t.datetime "updated_at", null: false
t.string "image_url"
t.bigint "user_id"
t.integer "quantity", default: 1
t.index ["category_id"], name: "index_products_on_category_id"
t.index ["user_id"], name: "index_products_on_user_id"
end
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment