Commit f0d57a97 by Trong Huu Nguyen

Merge branch 'dhp_cart' into 'development'

[Review] Cart feature

See merge request !3
parents 92e3a257 ccd4ffc7
...@@ -44,6 +44,14 @@ gem 'kaminari', '~> 1.0', '>= 1.0.1' ...@@ -44,6 +44,14 @@ gem 'kaminari', '~> 1.0', '>= 1.0.1'
gem 'devise', '~> 4.3' gem 'devise', '~> 4.3'
# Automatic Ruby code style checking tool. Aims to enforce the community-driven Ruby Style Guide. # Automatic Ruby code style checking tool. Aims to enforce the community-driven Ruby Style Guide.
gem 'rubocop', '~> 0.49.1' gem 'rubocop', '~> 0.49.1'
# Use jquery as the JavaScript library
gem 'jquery-rails'
# FontAwesome
gem 'font-awesome-rails', '~> 4.7', '>= 4.7.0.2'
# Bootstrap TouchSpin is a mobile and touch friendly input spinner component for Bootstrap
gem 'rails-assets-bootstrap-touchspin', source: 'https://rails-assets.org'
# Simple, efficient background processing for Ruby.
gem 'sidekiq', '~> 5.0', '>= 5.0.2'
# Use Capistrano for deployment # Use Capistrano for deployment
# gem 'capistrano-rails', group: :development # gem 'capistrano-rails', group: :development
...@@ -63,6 +71,8 @@ group :development do ...@@ -63,6 +71,8 @@ group :development do
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring' gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0' gem 'spring-watcher-listen', '~> 2.0.0'
# When mail is sent from your application, Letter Opener will open a preview in the browser instead of sending.
gem 'letter_opener', '~> 1.4', '>= 1.4.1'
end end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
......
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
remote: https://rails-assets.org/
specs: specs:
actioncable (5.1.1) actioncable (5.1.1)
actionpack (= 5.1.1) actionpack (= 5.1.1)
...@@ -72,6 +73,7 @@ GEM ...@@ -72,6 +73,7 @@ GEM
execjs execjs
coffee-script-source (1.12.2) coffee-script-source (1.12.2)
concurrent-ruby (1.0.5) concurrent-ruby (1.0.5)
connection_pool (2.2.1)
devise (4.3.0) devise (4.3.0)
bcrypt (~> 3.0) bcrypt (~> 3.0)
orm_adapter (~> 0.1) orm_adapter (~> 0.1)
...@@ -83,12 +85,18 @@ GEM ...@@ -83,12 +85,18 @@ GEM
faker (1.6.6) faker (1.6.6)
i18n (~> 0.5) i18n (~> 0.5)
ffi (1.9.18) ffi (1.9.18)
font-awesome-rails (4.7.0.2)
railties (>= 3.2, < 5.2)
globalid (0.4.0) globalid (0.4.0)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
i18n (0.8.4) i18n (0.8.4)
jbuilder (2.7.0) jbuilder (2.7.0)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
multi_json (>= 1.2) multi_json (>= 1.2)
jquery-rails (4.3.1)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
kaminari (1.0.1) kaminari (1.0.1)
activesupport (>= 4.1.0) activesupport (>= 4.1.0)
kaminari-actionview (= 1.0.1) kaminari-actionview (= 1.0.1)
...@@ -101,6 +109,10 @@ GEM ...@@ -101,6 +109,10 @@ GEM
activerecord activerecord
kaminari-core (= 1.0.1) kaminari-core (= 1.0.1)
kaminari-core (1.0.1) kaminari-core (1.0.1)
launchy (2.4.3)
addressable (~> 2.3)
letter_opener (1.4.1)
launchy (~> 2.2)
listen (3.1.5) listen (3.1.5)
rb-fsevent (~> 0.9, >= 0.9.4) rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7) rb-inotify (~> 0.9, >= 0.9.7)
...@@ -129,6 +141,8 @@ GEM ...@@ -129,6 +141,8 @@ GEM
public_suffix (2.0.5) public_suffix (2.0.5)
puma (3.9.1) puma (3.9.1)
rack (2.0.3) rack (2.0.3)
rack-protection (2.0.0)
rack
rack-test (0.6.3) rack-test (0.6.3)
rack (>= 1.0) rack (>= 1.0)
rails (5.1.1) rails (5.1.1)
...@@ -143,6 +157,12 @@ GEM ...@@ -143,6 +157,12 @@ GEM
bundler (>= 1.3.0, < 2.0) bundler (>= 1.3.0, < 2.0)
railties (= 5.1.1) railties (= 5.1.1)
sprockets-rails (>= 2.0.0) sprockets-rails (>= 2.0.0)
rails-assets-bootstrap (3.3.7)
rails-assets-jquery (>= 1.9.1, < 4)
rails-assets-bootstrap-touchspin (3.1.2)
rails-assets-bootstrap (>= 3.0.0)
rails-assets-jquery (>= 1.9.0)
rails-assets-jquery (3.2.1)
rails-dom-testing (2.0.3) rails-dom-testing (2.0.3)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
nokogiri (>= 1.6) nokogiri (>= 1.6)
...@@ -160,6 +180,7 @@ GEM ...@@ -160,6 +180,7 @@ GEM
rb-fsevent (0.9.8) rb-fsevent (0.9.8)
rb-inotify (0.9.8) rb-inotify (0.9.8)
ffi (>= 0.5.0) ffi (>= 0.5.0)
redis (3.3.3)
responders (2.4.0) responders (2.4.0)
actionpack (>= 4.2.0, < 5.3) actionpack (>= 4.2.0, < 5.3)
railties (>= 4.2.0, < 5.3) railties (>= 4.2.0, < 5.3)
...@@ -184,6 +205,11 @@ GEM ...@@ -184,6 +205,11 @@ GEM
childprocess (~> 0.5) childprocess (~> 0.5)
rubyzip (~> 1.0) rubyzip (~> 1.0)
websocket (~> 1.0) websocket (~> 1.0)
sidekiq (5.0.2)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
rack-protection (>= 1.5.0)
redis (~> 3.3, >= 3.3.3)
spring (2.0.2) spring (2.0.2)
activesupport (>= 4.2) activesupport (>= 4.2)
spring-watcher-listen (2.0.1) spring-watcher-listen (2.0.1)
...@@ -232,16 +258,21 @@ DEPENDENCIES ...@@ -232,16 +258,21 @@ DEPENDENCIES
coffee-rails (~> 4.2) coffee-rails (~> 4.2)
devise (~> 4.3) devise (~> 4.3)
faker (~> 1.6, >= 1.6.3) faker (~> 1.6, >= 1.6.3)
font-awesome-rails (~> 4.7, >= 4.7.0.2)
jbuilder (~> 2.5) jbuilder (~> 2.5)
jquery-rails
kaminari (~> 1.0, >= 1.0.1) kaminari (~> 1.0, >= 1.0.1)
letter_opener (~> 1.4, >= 1.4.1)
listen (>= 3.0.5, < 3.2) listen (>= 3.0.5, < 3.2)
mini_magick mini_magick
mysql2 (>= 0.3.18, < 0.5) mysql2 (>= 0.3.18, < 0.5)
puma (~> 3.7) puma (~> 3.7)
rails (~> 5.1.1) rails (~> 5.1.1)
rails-assets-bootstrap-touchspin!
rubocop (~> 0.49.1) rubocop (~> 0.49.1)
sass-rails (~> 5.0) sass-rails (~> 5.0)
selenium-webdriver selenium-webdriver
sidekiq (~> 5.0, >= 5.0.2)
spring spring
spring-watcher-listen (~> 2.0.0) spring-watcher-listen (~> 2.0.0)
turbolinks (~> 5) turbolinks (~> 5)
...@@ -250,4 +281,4 @@ DEPENDENCIES ...@@ -250,4 +281,4 @@ DEPENDENCIES
web-console (>= 3.3.0) web-console (>= 3.3.0)
BUNDLED WITH BUNDLED WITH
1.15.0 1.15.1
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Copyright (C) 2015 by original authors @ fontello.com</metadata>
<defs>
<font id="minicart-font" horiz-adv-x="1000" >
<font-face font-family="minicart-font" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
<missing-glyph horiz-adv-x="1000" />
<glyph glyph-name="glyph" unicode="&#xe800;" d="m225 600c0 25 19 45 43 45 25 0 45-20 45-45 0-23-20-43-45-43-24 0-43 20-43 43z m478 0c0 25 20 45 43 45 24 0 43-20 43-45 0-23-19-43-43-43-23 0-43 20-43 43z m-488 121c-65 0-115-55-115-121l-67-566c0-67 51-122 114-122l707 0c62 0 113 55 113 122l-67 566c0 66-50 121-115 121z m553 0c-26 110-135 192-266 192-131 0-240-82-266-192l80 0c24 70 98 123 186 123 88 0 162-53 186-123z" horiz-adv-x="1000" />
</font>
</defs>
</svg>
\ No newline at end of file
...@@ -10,6 +10,9 @@ ...@@ -10,6 +10,9 @@
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives. // about supported directives.
// //
//= require rails-ujs //= require jquery
//= require jquery_ujs
//= require bootstrap-touchspin
//# require rails-ujs
//= require turbolinks //= require turbolinks
//# require_tree . //# require_tree .
(function($) {
// Vertical Spinner - Touchspin - Product Details Quantity input
if ( $.fn.TouchSpin ) {
$('#product_quantity').TouchSpin({
verticalbuttons: true
});
$('.qty-input').TouchSpin();
$('.qty-input').on('change', function() {
$(this).closest('form').submit();
});
}
}).apply(this, [jQuery]);
\ No newline at end of file
(function($) {
}).apply(this, [jQuery]);
\ No newline at end of file
//= require cart/cart
\ No newline at end of file
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
* *
*# require_tree . *# require_tree .
*= require_self *= require_self
*= require bootstrap-touchspin
*= require custom *= require custom
*= require main_style *= require main_style
*= require skin_14 *= require skin_14
......
// "bootstrap-sprockets" must be imported before "bootstrap" and "bootstrap/variables" // "bootstrap-sprockets" must be imported before "bootstrap" and "bootstrap/variables"
@import "bootstrap-sprockets"; @import "bootstrap-sprockets";
@import "bootstrap"; @import "bootstrap";
@import "font-awesome";
\ No newline at end of file
@font-face {
font-family: 'minicart-font';
src: asset_url('minicart-font.eot?v=1.0');
src: asset_url('minicart-font.eot?#iefix&v=1.0') format("embedded-opentype"), asset_url('minicart-font.woff?v=1.0') format("woff"), asset_url('minicart-font.ttf?v=1.0') format("truetype"), asset_url('minicart-font.svg?v=1.0#minicart-font') format("svg");
font-weight: normal;
font-style: normal;
}
/* /*
* Common styles * Common styles
*/ */
...@@ -62,6 +70,7 @@ a { ...@@ -62,6 +70,7 @@ a {
width: 1280px; width: 1280px;
} }
} }
@media (min-width: 992px) { @media (min-width: 992px) {
.col-md-9 { .col-md-9 {
padding-right: 12px; padding-right: 12px;
...@@ -80,6 +89,111 @@ a { ...@@ -80,6 +89,111 @@ a {
} }
/* /*
* Header
*/
#header .header-logo {
float: left;
position: relative;
}
.minicart-icon:before {
font-family: "minicart-font" !important;
font-style: normal !important;
font-weight: normal !important;
font-variant: normal !important;
text-transform: none !important;
speak: none;
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.minicart-icon:before {
content: "\e800";
}
#header .header-row {
display: table-row;
clear: both;
}
#header .header-column {
display: table-cell;
vertical-align: middle;
}
#header .header-column .row {
margin: 0;
}
#header .header-column .header-row {
display: block;
clear: right;
}
#header .cart-area {
float: right;
vertical-align: middle;
}
@media (max-width: 991px) {
#header .cart-area {
margin-top: 5.5px;
}
}
#header .cart-dropdown {
position: relative;
display: inline-block;
vertical-align: middle;
padding-left: 7px;
padding-right: 7px;
}
#header .cart-dropdown .cart-dropdown-icon {
position: relative;
display: inline-block;
height: 40px;
padding: 0;
line-height: 40px;
text-align: center;
top: -1px;
color: #fff;
text-decoration: none !important;
}
#header .cart-dropdown .cart-dropdown-icon i {
font-size: 35px;
color: #000;
}
#header .cart-dropdown .cart-dropdown-icon .cart-info {
position: absolute;
width: 100%;
text-align: center;
top: 50%;
margin-top: -4px;
left: 0;
padding: 0;
display: block;
line-height: 1;
}
#header .cart-dropdown .cart-dropdown-icon .cart-info .cart-qty {
font-size: 14px;
font-weight: 600;
}
/*
* Button
*/
.btn.btn-default.hover-primary:hover, .btn.btn-default.hover-primary:focus {
color: #fff;
background-color: #000;
border-color: #000;
}
/*
* Products Grid * Products Grid
*/ */
.products-grid { .products-grid {
...@@ -87,36 +201,45 @@ a { ...@@ -87,36 +201,45 @@ a {
padding: 0; padding: 0;
margin: 0 -10px 0; margin: 0 -10px 0;
} }
.products-grid:after { .products-grid:after {
content: ''; content: '';
display: table; display: table;
clear: both; clear: both;
} }
.products-grid li { .products-grid li {
width: 100%; width: 100%;
padding: 10px 10px 0; padding: 10px 10px 0;
} }
.products-grid.columns6 { .products-grid.columns6 {
margin: 0 -5px; margin: 0 -5px;
} }
.products-grid.columns6 li { .products-grid.columns6 li {
padding-left: 5px; padding-left: 5px;
padding-right: 5px; padding-right: 5px;
} }
.products-grid.columns7 { .products-grid.columns7 {
margin: 0 -4px; margin: 0 -4px;
} }
.products-grid.columns7 li { .products-grid.columns7 li {
padding-left: 4px; padding-left: 4px;
padding-right: 4px; padding-right: 4px;
} }
.products-grid.columns8 { .products-grid.columns8 {
margin: 0 -3px; margin: 0 -3px;
} }
.products-grid.columns8 li { .products-grid.columns8 li {
padding-left: 3px; padding-left: 3px;
padding-right: 3px; padding-right: 3px;
} }
@media (min-width: 480px) { @media (min-width: 480px) {
.products-grid li { .products-grid li {
float: left; float: left;
...@@ -135,6 +258,7 @@ a { ...@@ -135,6 +258,7 @@ a {
clear: left; clear: left;
} }
} }
@media (min-width: 768px) { @media (min-width: 768px) {
.products-grid.columns3 li, .products-grid.columns4 li { .products-grid.columns3 li, .products-grid.columns4 li {
width: 33.33%; width: 33.33%;
...@@ -155,6 +279,7 @@ a { ...@@ -155,6 +279,7 @@ a {
clear: left; clear: left;
} }
} }
@media (min-width: 992px) { @media (min-width: 992px) {
.products-grid.columns6 li, .products-grid.columns7 li, .products-grid.columns8 li { .products-grid.columns6 li, .products-grid.columns7 li, .products-grid.columns8 li {
width: 20%; width: 20%;
...@@ -166,6 +291,7 @@ a { ...@@ -166,6 +291,7 @@ a {
clear: left; clear: left;
} }
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
.products-grid.columns4 li { .products-grid.columns4 li {
width: 25%; width: 25%;
...@@ -222,6 +348,7 @@ a { ...@@ -222,6 +348,7 @@ a {
padding: 0; padding: 0;
margin: 0 -10px 0; margin: 0 -10px 0;
} }
.products-list li { .products-list li {
padding: 10px; padding: 10px;
width: 100%; width: 100%;
...@@ -230,18 +357,21 @@ a { ...@@ -230,18 +357,21 @@ a {
.product { .product {
position: relative; position: relative;
} }
.product .product-image-area { .product .product-image-area {
overflow: hidden; overflow: hidden;
position: relative; position: relative;
background-color: #fff; background-color: #fff;
border-radius: 0; border-radius: 0;
} }
.product .product-image-area .product-image { .product .product-image-area .product-image {
display: block; display: block;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
border-radius: 0; border-radius: 0;
} }
.product .product-image-area .product-image img { .product .product-image-area .product-image img {
display: block; display: block;
width: 100%; width: 100%;
...@@ -250,6 +380,7 @@ a { ...@@ -250,6 +380,7 @@ a {
-moz-transition: all 0.3s; -moz-transition: all 0.3s;
transition: all 0.3s; transition: all 0.3s;
} }
.product .product-image-area .product-image .product-hover-image { .product .product-image-area .product-image .product-hover-image {
position: absolute; position: absolute;
left: 0; left: 0;
...@@ -257,6 +388,7 @@ a { ...@@ -257,6 +388,7 @@ a {
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
} }
.product .product-image-area .product-actions { .product .product-image-area .product-actions {
position: absolute; position: absolute;
width: 100%; width: 100%;
...@@ -270,6 +402,7 @@ a { ...@@ -270,6 +402,7 @@ a {
-moz-transition: opacity 0.2s; -moz-transition: opacity 0.2s;
transition: opacity 0.2s; transition: opacity 0.2s;
} }
.product .product-image-area .product-actions .addtocart, .product .product-image-area .product-actions .addtocart,
.product .product-image-area .product-actions .comparelink, .product .product-image-area .product-actions .comparelink,
.product .product-image-area .product-actions .addtowishlist { .product .product-image-area .product-actions .addtowishlist {
...@@ -289,6 +422,7 @@ a { ...@@ -289,6 +422,7 @@ a {
margin-top: 0; margin-top: 0;
margin-bottom: 0; margin-bottom: 0;
} }
.product .product-quickview { .product .product-quickview {
position: absolute; position: absolute;
right: 0; right: 0;
...@@ -306,17 +440,21 @@ a { ...@@ -306,17 +440,21 @@ a {
font-size: 12px; font-size: 12px;
line-height: 1.4; line-height: 1.4;
} }
.product .product-quickview span { .product .product-quickview span {
margin-left: 2px; margin-left: 2px;
} }
.product .product-quickview:hover, .product .product-quickview:focus { .product .product-quickview:hover, .product .product-quickview:focus {
opacity: 0.9 !important; opacity: 0.9 !important;
color: #fff; color: #fff;
text-decoration: none; text-decoration: none;
} }
.product .product-quickview:hover span, .product .product-quickview:focus span { .product .product-quickview:hover span, .product .product-quickview:focus span {
text-decoration: underline; text-decoration: underline;
} }
.product .product-label { .product .product-label {
position: absolute; position: absolute;
right: 10px; right: 10px;
...@@ -326,9 +464,11 @@ a { ...@@ -326,9 +464,11 @@ a {
z-index: 5; z-index: 5;
text-align: center; text-align: center;
} }
.product .product-label + .product-label { .product .product-label + .product-label {
top: 40px; top: 40px;
} }
.product .product-label span { .product .product-label span {
display: block; display: block;
position: relative; position: relative;
...@@ -338,56 +478,70 @@ a { ...@@ -338,56 +478,70 @@ a {
text-transform: uppercase; text-transform: uppercase;
border-radius: 0; border-radius: 0;
} }
.product .product-label span.discount { .product .product-label span.discount {
background-color: #e27c7c; background-color: #e27c7c;
} }
.product .product-label span.new { .product .product-label span.new {
background-color: #62b959; background-color: #62b959;
} }
.product .product-details-area { .product .product-details-area {
padding: 10px; padding: 10px;
text-align: center; text-align: center;
} }
.product .product-details-area .product-actions { .product .product-details-area .product-actions {
margin: 0 -10px; margin: 0 -10px;
} }
.product .product-name { .product .product-name {
color: #777; color: #777;
font-weight: 400; font-weight: 400;
font-size: 14px; font-size: 14px;
margin: 0 0 3px; margin: 0 0 3px;
} }
.product .product-name a { .product .product-name a {
color: #777; color: #777;
} }
.product .product-name a:hover, .product .product-name a:focus { .product .product-name a:hover, .product .product-name a:focus {
color: #000; color: #000;
text-decoration: none; text-decoration: none;
} }
.product:hover .product-image-area .product-actions { .product:hover .product-image-area .product-actions {
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
} }
.product:hover .product-image-area .product-actions .addtocart { .product:hover .product-image-area .product-actions .addtocart {
color: #000; color: #000;
background-color: #fff; background-color: #fff;
border: 1px solid #000; border: 1px solid #000;
} }
.product:hover .product-image-area .product-actions .addtocart:hover, .product:hover .product-image-area .product-actions .addtocart:focus { .product:hover .product-image-area .product-actions .addtocart:hover, .product:hover .product-image-area .product-actions .addtocart:focus {
color: #fff; color: #fff;
border-color: #000; border-color: #000;
background-color: #000; background-color: #000;
} }
.product:hover .product-image-area .product-actions .comparelink:hover, .product:hover .product-image-area .product-actions .comparelink:focus { .product:hover .product-image-area .product-actions .comparelink:hover, .product:hover .product-image-area .product-actions .comparelink:focus {
background-color: #52b9b5; background-color: #52b9b5;
} }
.product:hover .product-image-area .product-actions .addtowishlist:hover, .product:hover .product-image-area .product-actions .addtowishlist:focus { .product:hover .product-image-area .product-actions .addtowishlist:hover, .product:hover .product-image-area .product-actions .addtowishlist:focus {
background-color: #ed4949; background-color: #ed4949;
} }
.product:hover .product-image-area .product-image .product-hover-image { .product:hover .product-image-area .product-image .product-hover-image {
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
} }
.product:hover .product-quickview { .product:hover .product-quickview {
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
...@@ -397,20 +551,24 @@ a { ...@@ -397,20 +551,24 @@ a {
font-size: 0; font-size: 0;
margin: 5px 0 3px; margin: 5px 0 3px;
} }
.product-price-box .old-price, .product-price-box .old-price,
.product-price-box .product-price { .product-price-box .product-price {
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
font-weight: 600; font-weight: 600;
} }
.product-price-box .old-price { .product-price-box .old-price {
color: #999; color: #999;
font-size: 14px; font-size: 14px;
text-decoration: line-through; text-decoration: line-through;
} }
.product-price-box .old-price + .product-price { .product-price-box .old-price + .product-price {
margin-left: 6px; margin-left: 6px;
} }
.product-price-box .product-price { .product-price-box .product-price {
font-size: 20px; font-size: 20px;
color: #444; color: #444;
...@@ -427,9 +585,11 @@ a { ...@@ -427,9 +585,11 @@ a {
-moz-transition: all 0.3s; -moz-transition: all 0.3s;
transition: all 0.3s; transition: all 0.3s;
} }
.product-actions a:hover, .product-actions a:focus { .product-actions a:hover, .product-actions a:focus {
text-decoration: none; text-decoration: none;
} }
.product-actions a.addtocart { .product-actions a.addtocart {
color: #333; color: #333;
background-color: #fff; background-color: #fff;
...@@ -439,24 +599,29 @@ a { ...@@ -439,24 +599,29 @@ a {
line-height: 30px; line-height: 30px;
border: 1px solid #ccc; border: 1px solid #ccc;
} }
.product-actions a.addtocart i { .product-actions a.addtocart i {
font-size: 15px; font-size: 15px;
margin-right: 2px; margin-right: 2px;
} }
.product:hover .product-actions a.addtocart { .product:hover .product-actions a.addtocart {
background-color: #000; background-color: #000;
border-color: #000; border-color: #000;
color: #fff; color: #fff;
} }
.product-actions a.addtocart.outofstock { .product-actions a.addtocart.outofstock {
padding: 0 10px; padding: 0 10px;
cursor: default; cursor: default;
} }
.product:hover .product-actions a.addtocart.outofstock { .product:hover .product-actions a.addtocart.outofstock {
color: #333 !important; color: #333 !important;
background-color: #fff !important; background-color: #fff !important;
border-color: #ccc !important; border-color: #ccc !important;
} }
.product-actions a.addtowishlist, .product-actions a.comparelink, .product-actions a.quickview { .product-actions a.addtowishlist, .product-actions a.comparelink, .product-actions a.quickview {
font-size: 17px; font-size: 17px;
height: 32px; height: 32px;
...@@ -466,43 +631,52 @@ a { ...@@ -466,43 +631,52 @@ a {
visibility: hidden; visibility: hidden;
opacity: 0; opacity: 0;
} }
.hide-addtolinks .product-actions a.addtowishlist, .hide-addtolinks .product-actions a.comparelink, .hide-addtolinks .product-actions a.quickview { .hide-addtolinks .product-actions a.addtowishlist, .hide-addtolinks .product-actions a.comparelink, .hide-addtolinks .product-actions a.quickview {
display: none; display: none;
} }
.product-actions a.addtowishlist { .product-actions a.addtowishlist {
right: -37px; right: -37px;
color: #ed4949; color: #ed4949;
border: 1px solid #ed4949; border: 1px solid #ed4949;
} }
.product-actions a.addtowishlist:hover { .product-actions a.addtowishlist:hover {
color: #fff; color: #fff;
background-color: #ed4949; background-color: #ed4949;
} }
.product:hover .product-actions a.addtowishlist { .product:hover .product-actions a.addtowishlist {
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
right: 0; right: 0;
} }
.product-actions a.comparelink { .product-actions a.comparelink {
left: -37px; left: -37px;
color: #52b9b5; color: #52b9b5;
border: 1px solid #52b9b5; border: 1px solid #52b9b5;
} }
.product-actions a.comparelink:hover { .product-actions a.comparelink:hover {
color: #fff; color: #fff;
background-color: #52b9b5; background-color: #52b9b5;
} }
.product:hover .product-actions a.comparelink { .product:hover .product-actions a.comparelink {
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
left: 0; left: 0;
} }
.product-actions a.quickview { .product-actions a.quickview {
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
color: #000; color: #000;
border: 1px solid #000; border: 1px solid #000;
} }
.product-actions a.quickview:hover { .product-actions a.quickview:hover {
color: #fff; color: #fff;
background-color: #000; background-color: #000;
...@@ -513,39 +687,48 @@ a { ...@@ -513,39 +687,48 @@ a {
display: table; display: table;
clear: both; clear: both;
} }
.product.product-list .product-details-area { .product.product-list .product-details-area {
text-align: left; text-align: left;
padding: 0 20px; padding: 0 20px;
} }
.product.product-list .product-short-desc { .product.product-list .product-short-desc {
font-size: 14px; font-size: 14px;
line-height: 1.5; line-height: 1.5;
} }
.product.product-list .product-ratings { .product.product-list .product-ratings {
background: none; background: none;
margin-bottom: 10px; margin-bottom: 10px;
} }
.product.product-list .product-name { .product.product-list .product-name {
font-size: 18px; font-size: 18px;
margin: 10px 0; margin: 10px 0;
} }
.product.product-list .product-price-box { .product.product-list .product-price-box {
margin-top: 10px; margin-top: 10px;
} }
.product.product-list .product-actions { .product.product-list .product-actions {
margin: 0; margin: 0;
} }
.product.product-list .product-actions a.addtowishlist, .product.product-list .product-actions a.comparelink, .product.product-list .product-actions a.quickview { .product.product-list .product-actions a.addtowishlist, .product.product-list .product-actions a.comparelink, .product.product-list .product-actions a.quickview {
left: auto; left: auto;
right: auto; right: auto;
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
} }
.product.product-list .product-actions a.addtocart { .product.product-list .product-actions a.addtocart {
background-color: #000; background-color: #000;
border-color: #000; border-color: #000;
color: #fff; color: #fff;
} }
@media (min-width: 600px) { @media (min-width: 600px) {
.product.product-list .product-image-area { .product.product-list .product-image-area {
float: left; float: left;
...@@ -565,38 +748,47 @@ a { ...@@ -565,38 +748,47 @@ a {
padding: 6px 0 8px; padding: 6px 0 8px;
max-width: 300px; max-width: 300px;
} }
.product.product-sm:after { .product.product-sm:after {
content: ''; content: '';
display: table; display: table;
clear: both; clear: both;
} }
.product.product-sm .product-image-area { .product.product-sm .product-image-area {
width: 33%; width: 33%;
float: left; float: left;
padding: 2px; padding: 2px;
} }
.product.product-sm .product-details-area { .product.product-sm .product-details-area {
float: left; float: left;
width: 67%; width: 67%;
text-align: left; text-align: left;
padding: 10px 16px; padding: 10px 16px;
} }
.product.product-sm .product-name { .product.product-sm .product-name {
font-size: 12px; font-size: 12px;
} }
.product.product-sm .product-ratings { .product.product-sm .product-ratings {
background: none; background: none;
margin-bottom: 5px; margin-bottom: 5px;
} }
.product.product-sm .product-price-box { .product.product-sm .product-price-box {
margin: 2px 0 3px; margin: 2px 0 3px;
} }
.product.product-sm .product-price-box .old-price { .product.product-sm .product-price-box .old-price {
font-size: 11px; font-size: 11px;
} }
.product.product-sm .product-price-box .old-price + .product-price { .product.product-sm .product-price-box .old-price + .product-price {
margin-left: 3px; margin-left: 3px;
} }
.product.product-sm .product-price-box .product-price { .product.product-sm .product-price-box .product-price {
font-size: 15px; font-size: 15px;
} }
...@@ -608,21 +800,26 @@ a { ...@@ -608,21 +800,26 @@ a {
text-align: center; text-align: center;
margin-top: 20px; margin-top: 20px;
} }
.toolbar-bottom .toolbar { .toolbar-bottom .toolbar {
display: inline-block; display: inline-block;
} }
.toolbar { .toolbar {
margin-bottom: 8px; margin-bottom: 8px;
} }
.toolbar .sorter:after { .toolbar .sorter:after {
content: ''; content: '';
display: table; display: table;
clear: both; clear: both;
} }
.toolbar .sorter .pagination { .toolbar .sorter .pagination {
float: right; float: right;
margin: 0 0 5px 8px; margin: 0 0 5px 8px;
} }
.toolbar .sorter .pagination a, .toolbar .sorter .pagination a,
.toolbar .sorter .pagination span { .toolbar .sorter .pagination span {
border-radius: 0; border-radius: 0;
...@@ -638,40 +835,50 @@ a { ...@@ -638,40 +835,50 @@ a {
.form-section { .form-section {
overflow: hidden; overflow: hidden;
} }
.form-section .featured-box { .form-section .featured-box {
margin-bottom: 30px; margin-bottom: 30px;
} }
.form-section .featured-box .box-content { .form-section .featured-box .box-content {
padding: 35px 25px; padding: 35px 25px;
} }
@media (min-width: 768px) { @media (min-width: 768px) {
.form-section .form-content { .form-section .form-content {
min-height: 275px; min-height: 275px;
} }
} }
.form-section p { .form-section p {
margin-bottom: 15px; margin-bottom: 15px;
} }
.form-section .form-action { .form-section .form-action {
padding-top: 8px; padding-top: 8px;
margin-top: 1em; margin-top: 1em;
text-align: right; text-align: right;
} }
.form-section .form-action a:not(.btn) { .form-section .form-action a:not(.btn) {
color: #000; color: #000;
} }
.form-section .form-action a:not(.btn).pull-left { .form-section .form-action a:not(.btn).pull-left {
line-height: 38px; line-height: 38px;
} }
.form-section .form-action a:not(.btn):hover, .form-section .form-action a:not(.btn):focus { .form-section .form-action a:not(.btn):hover, .form-section .form-action a:not(.btn):focus {
color: #050505; color: #050505;
text-decoration: underline; text-decoration: underline;
} }
.form-section .required { .form-section .required {
color: #eb340a; color: #eb340a;
font-size: 11px; font-size: 11px;
text-align: right; text-align: right;
} }
@media (min-width: 992px) { @media (min-width: 992px) {
.form-section.register-form .row { .form-section.register-form .row {
margin-left: -30px; margin-left: -30px;
...@@ -682,12 +889,14 @@ a { ...@@ -682,12 +889,14 @@ a {
padding-right: 30px; padding-right: 30px;
} }
} }
.form-control { .form-control {
border-radius: 0; border-radius: 0;
height: 38px; height: 38px;
padding-top: 8px; padding-top: 8px;
padding-bottom: 8px; padding-bottom: 8px;
} }
.form-group .field_with_errors { .form-group .field_with_errors {
display: inline; display: inline;
} }
...@@ -697,7 +906,7 @@ a { ...@@ -697,7 +906,7 @@ a {
*/ */
.featured-box { .featured-box {
background: #F5F5F5; background: #F5F5F5;
background: -webkit-linear-gradient( top , #FFF 1%, #fcfcfc 98%) repeat scroll 0 0 transparent; background: -webkit-linear-gradient(top, #FFF 1%, #fcfcfc 98%) repeat scroll 0 0 transparent;
background: linear-gradient(to bottom, #FFF 1%, #fcfcfc 98%) repeat scroll 0 0 transparent; background: linear-gradient(to bottom, #FFF 1%, #fcfcfc 98%) repeat scroll 0 0 transparent;
-webkit-box-sizing: border-box; -webkit-box-sizing: border-box;
-moz-box-sizing: border-box; -moz-box-sizing: border-box;
...@@ -721,6 +930,7 @@ html .featured-box-primary .box-content { ...@@ -721,6 +930,7 @@ html .featured-box-primary .box-content {
.featured-box { .featured-box {
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.08); box-shadow: 0 2px 3px rgba(0, 0, 0, 0.08);
} }
.featured-box .box-content { .featured-box .box-content {
border-radius: 8px; border-radius: 8px;
border-top: 4px solid #CCC; border-top: 4px solid #CCC;
...@@ -728,7 +938,342 @@ html .featured-box-primary .box-content { ...@@ -728,7 +938,342 @@ html .featured-box-primary .box-content {
position: relative; position: relative;
top: -1px; top: -1px;
} }
/* Featured Box Left */ /* Featured Box Left */
.featured-box-text-left { .featured-box-text-left {
text-align: left; text-align: left;
} }
/*
* Product details
*/
.product-details-box .product-detail-qty {
display: inline-block;
vertical-align: middle;
margin-right: 7px;
width: 60px;
}
.product-details-box .product-detail-qty #product_quantity {
border-radius: 0;
width: 35px !important;
border-color: #e1e1e1;
color: #000;
padding-left: 2px;
padding-right: 2px;
text-align: center;
}
.product-details-box .product-detail-qty .btn {
border-radius: 0;
}
.product-details-box .product-detail-qty .btn.btn-default {
color: #ccc;
font-size: 8px;
border-color: #e1e1e1;
}
.product-details-box .product-detail-qty .btn.btn-default.bootstrap-touchspin-up {
margin-top: -2px;
}
.product-details-box .product-detail-qty .btn.btn-default.bootstrap-touchspin-down {
margin-bottom: -2px;
}
.product-details-box .product-actions {
margin-top: 10px;
padding-bottom: 10px;
margin-bottom: 20px;
border-bottom: 1px solid #ebebeb;
}
.product-details-box .product-actions:after {
content: '';
display: table;
clear: both;
}
.product-details-box .product-actions .addtocart {
color: #fff;
border-color: #000;
background-color: #000;
line-height: 36px;
height: 38px;
min-width: 160px;
text-align: center;
vertical-align: middle;
}
.product-details-box .product-actions .addtocart:hover, .product-details-box .product-actions .addtocart:focus {
color: #fff;
border-color: #0f0f0f;
background-color: #0f0f0f;
}
.bootstrap-touchspin .input-group-btn-vertical {
position: relative;
white-space: nowrap;
width: 1%;
vertical-align: middle;
display: table-cell;
}
.bootstrap-touchspin .input-group-btn-vertical > .btn {
display: block;
float: none;
width: 100%;
max-width: 100%;
padding: 9px 10px;
margin-left: -1px;
position: relative;
}
.bootstrap-touchspin .input-group-btn-vertical .bootstrap-touchspin-up {
border-radius: 0;
}
.bootstrap-touchspin .input-group-btn-vertical .bootstrap-touchspin-down {
margin-top: -2px;
border-radius: 0;
}
.bootstrap-touchspin .input-group-btn-vertical i {
position: absolute;
top: 5px;
left: 6px;
font-size: 9px;
font-weight: normal;
}
/*
* Cart table
*/
.cart-table-wrap {
border: 1px solid #ececec;
border-radius: 0;
background: #fff;
display: block;
padding: 30px;
margin-bottom: 50px;
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.08);
}
@media (min-width: 992px) {
.cart-table-wrap {
margin-bottom: 60px;
}
}
.cart-table {
width: 100%;
border: 0;
border-spacing: 0;
font-size: 14px;
}
.cart-table thead tr {
border-bottom: 1px solid #dcdcdc;
}
.cart-table thead tr th {
font-weight: 600;
padding: 15px 10px;
color: #777;
white-space: nowrap;
vertical-align: middle;
line-height: 1;
}
.cart-table tbody tr td {
border-bottom: 1px solid #dcdcdc;
padding: 15px 10px;
line-height: 1.3;
}
.cart-table tbody tr td.product-action-td {
padding-left: 0;
padding-right: 0;
}
.cart-table tbody tr td.product-image-td a {
display: block;
}
.cart-table tbody tr td.product-image-td a img {
display: block;
width: 100px;
height: auto;
}
.cart-table tbody tr td.product-name-td h2 {
font-size: 14px;
font-weight: 400;
margin-bottom: 0;
}
.cart-table tbody tr td.product-name-td h2 a {
color: #000;
}
.cart-table tbody tr td.product-name-td h2 a:hover, .cart-table tbody tr td.product-name-td h2 a:focus {
color: #000;
}
.cart-table tfoot td {
padding: 15px 5px 0;
}
.cart-table tfoot .btn.btn-default.btn-continue {
float: left;
}
.cart-table tfoot .btn.btn-default.btn-update {
float: right;
margin-left: 10px;
}
.cart-table tfoot .btn.btn-default.btn-clear {
float: right;
}
@media (max-width: 1199px) {
.cart-table thead {
display: none;
}
.cart-table tbody tr {
position: relative;
display: block;
border-bottom: 1px solid #dcdcdc;
padding: 25px 0;
}
.cart-table tbody tr td {
display: block;
padding: 0 0 15px;
width: 100%;
border-width: 0;
text-align: center !important;
}
.cart-table tbody tr td:last-child {
padding-bottom: 0;
}
.cart-table tbody tr td .qty-holder {
width: 90px;
}
.cart-table tbody tr td.product-action-td {
position: absolute;
top: 20px;
z-index: 1;
}
.cart-table tbody tr td.product-action-td .btn-remove {
float: right;
}
.cart-table tbody tr td.product-image-td {
padding-bottom: 15px;
}
.cart-table tbody tr td.product-image-td a img {
margin: 0 auto;
}
.cart-table tbody tr:first-child {
padding-top: 0;
}
.cart-table tbody tr:first-child td.product-action-td {
top: -5px;
}
.cart-table tfoot td {
padding-top: 25px;
}
.cart-table tfoot .btn.btn-default.btn-continue, .cart-table tfoot .btn.btn-default.btn-update, .cart-table tfoot .btn.btn-default.btn-clear {
float: none;
display: block;
width: 100%;
margin: 0 0 10px;
}
}
.cart .sidebar.shop-sidebar .panel .btn-block + .btn-block {
margin-top: 0;
}
.cart .sidebar.shop-sidebar .panel .btn-link {
font-size: 13px;
}
.qty-input {
display: inline-block;
vertical-align: middle;
width: 62px !important;
font-size: 14px;
text-align: center;
color: #777;
height: 34px;
border-radius: 0;
border: 1px solid #ccc;
margin: 0 -1px;
outline: none;
}
.qty-holder {
display: inline-block;
width: 125px;
white-space: nowrap;
vertical-align: middle;
font-size: 0;
}
/*
* Totals table
*/
.totals-table {
width: 100%;
margin-bottom: 5px;
}
.totals-table tbody tr {
border-bottom: 1px solid #dcdcdc;
}
.totals-table tbody tr:last-child {
border-bottom: none;
}
.totals-table tbody tr td {
padding: 10px;
line-height: 1.4;
font-size: 15px;
font-weight: 300;
text-align: left !important;
}
.totals-table tbody tr td:last-child {
color: #000;
text-align: right !important;
font-weight: 600;
}
.totals-table tbody tr:last-child td:last-child {
font-size: 17px;
}
/*
* Checkout
*/
.form-col {
color: #393939;
margin-bottom: 30px;
}
.form-col .checkout-review-action {
border-top: 1px solid #b6b6b6;
}
.form-col .checkout-review-action h5 {
color: #3f3f3f;
font-size: 16px;
font-weight: bold;
margin-top: 30px;
margin-bottom: 10px;
}
.form-col .checkout-review-action h5 span {
margin-left: 45px;
}
\ No newline at end of file
body { body {
font-size: 13px; font-size: 13px;
font-family: "Open Sans", Arial, sans-serif;
color: #777;
line-height: 1.5; line-height: 1.5;
background: image-url('shop-14-bg.png') top center repeat-x; background: transparent image-url('shop-14-bg.png') repeat-x top center;
} }
a { a {
color: #000000; color: #000000;
...@@ -139,6 +141,8 @@ html .btn-primary:active:focus { ...@@ -139,6 +141,8 @@ html .btn-primary:active:focus {
#header .header-container { #header .header-container {
padding-top: 28px; padding-top: 28px;
padding-bottom: 28px; padding-bottom: 28px;
position: relative;
display: table;
} }
#header .header-container.header-nav { #header .header-container.header-nav {
padding: 0; padding: 0;
......
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
include CartsHelper
protect_from_forgery with: :exception protect_from_forgery with: :exception
# rescue_from ActiveRecord::RecordNotFound, :with => :render_404
rescue_from ActiveRecord::RecordNotFound, :with => :render_404
before_action :set_cart
before_action :configure_permitted_parameters, if: :devise_controller?
def render_404 def render_404
respond_to do |format| respond_to do |format|
...@@ -9,4 +15,12 @@ class ApplicationController < ActionController::Base ...@@ -9,4 +15,12 @@ class ApplicationController < ActionController::Base
format.any { head :not_found } format.any { head :not_found }
end end
end end
protected
def configure_permitted_parameters
added_attrs = [:username, :first_name, :last_name, :email, :password, :password_confirmation]
devise_parameter_sanitizer.permit :sign_up, keys: added_attrs
devise_parameter_sanitizer.permit :account_update, keys: added_attrs
end
end end
class CartsController < ApplicationController
include CartsHelper
include LineItemsHelper
before_action :set_cart
before_action :get_line_items
rescue_from ActiveRecord::RecordNotFound, with: :invalid_cart
# DELETE /cart/remove
def destroy
destroy_cart_session
respond_to do |format|
format.html { redirect_to root_url }
end
end
private
def invalid_cart
redirect_to root_url
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
# GET /orders/new
def new
redirect_to root_url if @cart.blank?
@order = Order.new
end
# POST /orders
def create
@order = current_user.orders.create
create_line_items_for_order(@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
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'])
end
end
end
\ No newline at end of file
...@@ -3,5 +3,6 @@ class StaticPagesController < ApplicationController ...@@ -3,5 +3,6 @@ class StaticPagesController < ApplicationController
@latest_products = Product.page(params[:page]).per(10) @latest_products = Product.page(params[:page]).per(10)
# @TODO: Get recommended products # @TODO: Get recommended products
@recommended_products = Product.last(6) @recommended_products = Product.last(6)
# session.clear
end end
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)
end
def destroy_cart_session
session[:cart] = nil
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
end
def cart_total_items
return 0 if @cart.nil?
@cart.sum { |_, product_attrs| product_attrs[:quantity.to_s] || product_attrs[:quantity] }
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 OrderMailer < ApplicationMailer
def send_order_detail_to_user(user, order)
@user = user
@order = order
mail to: user.email, subject: "Order detail #{@order.id}"
end
end
class LineItem < ApplicationRecord
belongs_to :product, optional: true
belongs_to :order, optional: true
def total_price
product.price * quantity
end
end
\ No newline at end of file
class Order < ApplicationRecord
has_many :line_items, dependent: :destroy
belongs_to :user
def total_price
line_items.to_a.sum { |item| item.product.price * item.quantity }
end
end
\ No newline at end of file
...@@ -2,6 +2,20 @@ class User < ApplicationRecord ...@@ -2,6 +2,20 @@ class User < ApplicationRecord
# Include default devise modules. Others available are: # Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable # :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable :recoverable, :rememberable, :trackable,
:validatable, :authentication_keys => [:login]
attr_accessor :login
has_many :products, dependent: :destroy has_many :products, dependent: :destroy
has_many :orders, dependent: :destroy
validates :username, format: { with: /^[a-zA-Z0-9_\.]*$/, multiline: true }
def self.find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
login = conditions.delete(:login)
where(conditions).where(["lower(username) = :value OR lower(email) = :value", {value: login.strip.downcase}]).first
end
def validate_username
errors.add(:username, :invalid) if User.where(email: username).exists?
end
end end
<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' %>
</td>
</tr>
\ No newline at end of file
<%= cart_total_items %>
\ No newline at end of file
<table class="cart-table">
<thead>
<tr>
<th></th>
<th></th>
<th>Product Name</th>
<th>Unit Price</th>
<th>Qty</th>
<th>Subtotal</th>
</tr>
</thead>
<tbody>
<% @line_items.each do |id, line_item| %>
<%= render partial: 'line_item', locals: { id: id, line_item: line_item } %>
<% end %>
</tbody>
<tfoot>
<%= render 'cart_actions' %>
</tfoot>
</table>
\ No newline at end of file
<tr>
<td class="product-action-td">
<%= link_to fa_icon('times'), line_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]) %>
</h2>
</td>
<td><%= number_to_currency(line_item[:product].price) %></td>
<td>
<%= form_tag line_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' %>
</div>
<% end %>
</td>
<td><%= number_to_currency(line_item[:product].price * line_item[:quantity]) %></td>
</tr>
\ No newline at end of file
<div class="cart">
<div class="container">
<h1 class="h2 heading-primary mt-lg clearfix">
Shopping Cart
</h1>
<div class="row">
<div class="col-md-8 col-lg-9">
<div class="cart-table-wrap">
<% if notice.presence %>
<div class="alert alert-success"><%= notice %></div>
<% end %>
<%= render 'cart_table' %>
</div>
</div>
<aside class="col-md-4 col-lg-3 sidebar shop-sidebar">
<div class="panel-group">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">Cart Totals</h4>
</div>
<div id="panel-cart-total">
<div class="panel-body">
<table class="totals-table">
<tbody>
<tr>
<td>Grand Total</td>
<td><%= number_to_currency(cart_total_price) %></td>
</tr>
</tbody>
</table>
<div class="totals-table-action">
<%= button_to 'Proceed to checkout', new_order_path, method: :get, class: 'btn btn-primary btn-block' %>
</div>
</div>
</div>
</div>
</div>
</aside>
</div>
</div>
</div>
\ No newline at end of file
...@@ -10,12 +10,12 @@ ...@@ -10,12 +10,12 @@
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<div class="form-content"> <div class="form-content">
<h3 class="heading-text-color font-weight-normal"> <h3 class="heading-text-color font-weight-normal">
Registered Customers
</h3> </h3>
<div class="form-group"> <div class="form-group">
<%= f.label :email %> <%= f.label :login %>
<%= f.email_field :email, autofocus: true, class: "form-control" %> <%= f.email_field :login, autofocus: true, class: "form-control" %>
</div> </div>
<div class="form-group"> <div class="form-group">
......
...@@ -9,17 +9,3 @@ ...@@ -9,17 +9,3 @@
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
<%= link_to "Forgot your password?", new_password_path(resource_name) %><br /> <%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
<% end -%> <% end -%>
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.omniauthable? %>
<%- resource_class.omniauth_providers.each do |provider| %>
<%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %><br />
<% end -%>
<% end -%>
...@@ -14,9 +14,16 @@ ...@@ -14,9 +14,16 @@
</div> </div>
</div> </div>
<div class="header-column"> <div class="header-column">
<div class="header-row"> <div class="row">
<div class="cart-area"> <div class="cart-area">
<div class="cart-dropdown">
<a href="<%= cart_index_url %>" class="cart-dropdown-icon">
<i class="minicart-icon"></i>
<span class="cart-info">
<span class="cart-qty"><%= cart_total_items %></span>
</span>
</a>
</div>
</div> </div>
<div class="header-search"> <div class="header-search">
......
...@@ -14,5 +14,6 @@ ...@@ -14,5 +14,6 @@
<%= yield %> <%= yield %>
</div> </div>
<%= render 'layouts/footer' %> <%= render 'layouts/footer' %>
<%= javascript_include_tag 'theme-main' %>
</body> </body>
</html> </html>
<tr>
<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]) %>
</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>
</tr>
\ No newline at end of file
$('.cart-qty').html("<%= render 'carts/cart_quantity' %>");
\ No newline at end of file
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<title></title>
<style type="text/css">
body {
}
.ExternalClass table {
border-collapse: separate;
}
a, a:link, a:visited {
text-decoration: none;
color: #00788a
}
a:hover {
text-decoration: underline;
}
h2, h2 a, h2 a:visited, h3, h3 a, h3 a:visited, h4, h5, h6 {
color: #000 !important
}
.ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td {
line-height: 100%
}
.ExternalClass * {
line-height: 100%
}
</style>
</head>
<body style="margin: 0; padding: 0;">
<table width="100%" cellpadding="10" cellspacing="0" border="0" bgcolor="#CCCCCC" align="center">
<tr>
<td>
<table width="600" align="center" cellpadding="10" cellspacing="0" border="0" bgcolor="#FFFFFF" style="border-collapse: collapse; font-family: Arial, Helvetica, sans-serif; font-size: 12px; margin: 0 auto;">
<tr>
<td>
<table align="center" width="580" border="0" cellpadding="10" cellspacing="0" class="html-email" style="border-collapse: collapse; font-family: Arial, Helvetica, sans-serif; font-size: 12px;">
<tr>
<td width="580" colspan="2" style="border: 1px solid #CCCCCC;">
<strong>Hello <%= @user.email %>,</strong>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table align="center" width="580" border="0" cellpadding="10" cellspacing="0" class="html-email" style="border-collapse: collapse; font-family: Arial, Helvetica, sans-serif; font-size: 12px;">
<tr>
<td align="left" style="border: 1px solid #CCCCCC;">
Your order number: <br/>
<strong><%= @order.id %></strong>
</td>
<td align="left" style="border: 1px solid #CCCCCC;">
Your order total: <br/>
<strong><%= number_to_currency(@order.total_price) %></strong>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table align="center" class="html-email" width="580" cellspacing="0" cellpadding="5" border="0" style="border-collapse: collapse; font-size: 12px;">
<tr align="left" class="sectiontableheader">
<th align="left" bgcolor="#EEEEEE" style="border: 1px solid #CCCCCC;">SKU</th>
<th align="center" bgcolor="#EEEEEE" colspan="2" style="border: 1px solid #CCCCCC;">Product Name</th>
<th align="center" bgcolor="#EEEEEE" style="border: 1px solid #CCCCCC;">Qty</th>
<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| %>
<tr valign="top">
<td align="left" style="border: 1px solid #CCCCCC;">
<%= item.product.id %>
</td>
<td align="center" colspan="2" style="border: 1px solid #CCCCCC;">
<%= item.product.title %>
</td>
<td align="right" style="border: 1px solid #CCCCCC;">
<%= item.quantity %>
</td>
<td align="right" class="priceCol" style="border: 1px solid #CCCCCC; white-space: nowrap;">
<span class='priceColor2'><%= number_to_currency(item.product.price) %></span>
</td>
<td align="right" class="priceCol" style="border: 1px solid #CCCCCC; white-space: nowrap;">
<span class='priceColor2'><%= number_to_currency(item.product.price * item.quantity) %></span>
</td>
</tr>
<% end %>
<tr>
<td align="right" class="pricePad" colspan="5" style="border: 1px solid #CCCCCC;"><strong>Total</strong></td>
<td align="right" style="border: 1px solid #CCCCCC; white-space: nowrap;">
<strong><%= number_to_currency(@order.total_price) %></strong>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
<%= @user.email %>
Your order has been created
\ No newline at end of file
<div class="checkout">
<div class="container">
<h1 class="h2 heading-primary mt-lg mb-md clearfix">
Checkout
</h1>
<div class="row">
<div class="col-md-12">
<div class="form-col">
<h3>Your Information</h3>
<div class="row">
<div class="col-xs-12">
<div class="form-group">
<label for="email">Email:</label>
<input type="text" class="form-control" value="<%= current_user.email %>" disabled>
</div>
</div>
</div>
</div>
<div class="cart-table-wrap">
<table class="cart-table">
<thead>
<tr>
<th></th>
<th>Product Name</th>
<th>Unit Price</th>
<th>Qty</th>
<th>Subtotal</th>
</tr>
</thead>
<tbody>
<% @line_items.each do |id, line_item| %>
<%= render partial: 'line_items/line_item', locals: {id: id, line_item: line_item} %>
<% end %>
</tbody>
</table>
</div>
<div class="form-col">
<div class="checkout-review-action">
<h5>Grand Total <span><%= number_to_currency(cart_total_price) %></span></h5>
<%= form_for @order do |f| %>
<%= f.submit 'Place Order now', class: 'btn btn-primary' %>
<% end %>
</div>
</div>
</div>
</div>
</div>
</div>
\ No newline at end of file
...@@ -21,6 +21,14 @@ ...@@ -21,6 +21,14 @@
<span class="product-price"><%= number_to_currency(@product.price) %></span> <span class="product-price"><%= number_to_currency(@product.price) %></span>
</div> </div>
</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>
</div> </div>
......
...@@ -14,5 +14,6 @@ module DhpVenshop ...@@ -14,5 +14,6 @@ module DhpVenshop
# Settings in config/environments/* take precedence over those specified here. # Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers # Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded. # -- all .rb files in that directory are automatically loaded.
config.assets.paths << Rails.root.join('app', 'assets', 'fonts')
end end
end end
...@@ -51,4 +51,6 @@ Rails.application.configure do ...@@ -51,4 +51,6 @@ Rails.application.configure do
# Use an evented file watcher to asynchronously detect changes in source code, # Use an evented file watcher to asynchronously detect changes in source code,
# routes, locales, etc. This feature depends on the listen gem. # routes, locales, etc. This feature depends on the listen gem.
config.file_watcher = ActiveSupport::EventedFileUpdateChecker config.file_watcher = ActiveSupport::EventedFileUpdateChecker
config.action_mailer.delivery_method = :letter_opener
end end
...@@ -12,3 +12,4 @@ Rails.application.config.assets.paths << Rails.root.join('node_modules') ...@@ -12,3 +12,4 @@ Rails.application.config.assets.paths << Rails.root.join('node_modules')
# application.js, application.css, and all non-JS/CSS in the app/assets # application.js, application.css, and all non-JS/CSS in the app/assets
# folder are already added. # folder are already added.
# Rails.application.config.assets.precompile += %w( admin.js admin.css ) # Rails.application.config.assets.precompile += %w( admin.js admin.css )
Rails.application.config.assets.precompile += %w( theme-main.js )
...@@ -34,7 +34,7 @@ Devise.setup do |config| ...@@ -34,7 +34,7 @@ Devise.setup do |config|
# session. If you need permissions, you should implement that in a before filter. # session. If you need permissions, you should implement that in a before filter.
# You can also supply a hash where the value is a boolean determining whether # You can also supply a hash where the value is a boolean determining whether
# or not authentication should be aborted when the value is not present. # or not authentication should be aborted when the value is not present.
# config.authentication_keys = [:email] config.authentication_keys = [:login]
# Configure parameters from the request object used for authentication. Each entry # Configure parameters from the request object used for authentication. Each entry
# given should be a request method and it will automatically be passed to the # given should be a request method and it will automatically be passed to the
......
...@@ -3,5 +3,9 @@ Rails.application.routes.draw do ...@@ -3,5 +3,9 @@ Rails.application.routes.draw do
root 'static_pages#index' root 'static_pages#index'
resources :categories resources :categories
resources :products 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'
devise_for :users devise_for :users
end end
class CreateLineItems < ActiveRecord::Migration[5.1]
def change
create_table :line_items do |t|
t.integer :quantity, default: 1
t.references :product, index: true
t.references :order, index: true
t.timestamps
end
end
end
class CreateOrders < ActiveRecord::Migration[5.1]
def change
create_table :orders do |t|
t.references :user, index: true
t.timestamps
end
end
end
class AddCustomFieldsToUsers < ActiveRecord::Migration[5.1]
def change
add_column :users, :username, :string
add_column :users, :first_name, :string
add_column :users, :last_name, :string
end
end
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170608073720) do ActiveRecord::Schema.define(version: 20170613012113) do
create_table "categories", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t| create_table "categories", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
t.string "title" t.string "title"
...@@ -68,5 +68,4 @@ ActiveRecord::Schema.define(version: 20170608073720) do ...@@ -68,5 +68,4 @@ ActiveRecord::Schema.define(version: 20170608073720) do
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end end
add_foreign_key "products", "users"
end end
...@@ -36,9 +36,3 @@ categories = Category.all ...@@ -36,9 +36,3 @@ categories = Category.all
user_id: User.pluck(:id).shuffle[1]) user_id: User.pluck(:id).shuffle[1])
end end
end end
# Create super user
User.create!(email: 'admin@example.com',
password: 'password',
password_confirmation: 'password',
super_admin: true)
\ No newline at end of file
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