Browse Source

Shop basket progress

Vova Tkach 5 years ago
parent
commit
1de1bfe3bd

+ 10 - 2
assets/template/header_html_file.go

@@ -26,7 +26,8 @@ var VarHeaderHtmlFile = []byte(`<!doctype html>
 			<nav class="navbar navbar-expand-lg navbar-light bg-light">
 				<div class="container">
 					<a class="navbar-brand" href="/">Fave {{$.System.InfoVersion}}</a>
-					<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
+					<button class="navbar-toggler collapsed" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
+						<span id="basket-mobile-btn" class="badge badge-pill badge-primary">{{$.Data.ShopBasketProductsCount}}</span>
 						<span class="navbar-toggler-icon"></span>
 					</button>
 					<div class="collapse navbar-collapse" id="navbarResponsive">
@@ -50,7 +51,14 @@ var VarHeaderHtmlFile = []byte(`<!doctype html>
 								<a class="nav-link{{if eq $.Data.Module "404"}} active{{end}}" href="/not-existent-page/">404</a>
 							</li>
 							<li class="nav-item">
-								<a id="basket-nav-btn" class="nav-link" href="" onclick="window&&window.frontend&&frontend.ShopBasketOpen(this);return false;">Basket <span class="badge badge-pill badge-primary">{{$.Data.ShopBasketProductsCount}}</span></a>
+								<a id="basket-nav-btn" class="nav-link" href="" onclick="window&&window.frontend&&frontend.ShopBasketBtnCollapse()&&frontend.ShopBasketOpen(this);return false;">Basket <span class="badge badge-pill badge-primary">{{$.Data.ShopBasketProductsCount}}</span></a>
+							</li>
+							<li>
+								<select class="form-control" onchange="document.location.reload(true);">
+									{{range $.Data.Shop.Currencies}}
+										<option value="{{.Id}}"{{if eq .Id $.Data.Shop.CurrentCurrency.Id}} selected{{end}}>{{.Code}}</option>
+									{{end}}
+								</select>
 							</li>
 						</ul>
 					</div>

+ 102 - 31
assets/template/scripts_js_file.go

@@ -48,6 +48,7 @@ var VarScriptsJsFile = []byte(`(function(window, $) {
 
 		function ShopBasketSetNavBtnProductsCount(value) {
 			$('#basket-nav-btn .badge').html(value);
+			$('#basket-mobile-btn').html(value);
 		};
 
 		function ShopBasketAjaxCommand(cmd, product_id, success, fail, always) {
@@ -64,13 +65,13 @@ var VarScriptsJsFile = []byte(`(function(window, $) {
 			});
 		};
 
-		function ShopBasketAjaxGetCount(success, fail, always) {
+		function ShopBasketAjaxGetInfo(success, fail, always) {
 			$.ajax({
 				type: "GET",
 				dataType: 'json',
 				url: '/shop/basket/info/'
 			}).done(function(data) {
-				if(success && data && data.total_count) { success(data.total_count); }
+				if(success && data) { success(data); }
 			}).fail(function(xhr, status, error) {
 				if(fail) { fail(xhr, status, error); }
 			}).always(function() {
@@ -78,17 +79,67 @@ var VarScriptsJsFile = []byte(`(function(window, $) {
 			});
 		};
 
+		function ShopBasketAjaxGetCount(success, fail, always) {
+			ShopBasketAjaxGetInfo(function(data) {
+				if(success && data && data.total_count != undefined) {
+					success(data.total_count);
+				}
+			}, function(xhr, status, error) {
+				if(fail) { fail(xhr, status, error); }
+			}, function() {
+				if(always) { always(); }
+			});
+		};
+
 		function ShopBasketAjaxUpdateCount() {
 			ShopBasketAjaxGetCount(function(count) {
 				ShopBasketSetNavBtnProductsCount(count);
 			});
 		};
 
+		function ShopBasketAjaxProductsHtml(success, fail, always) {
+			ShopBasketAjaxGetInfo(function(data) {
+				if(data) {
+					if(data.total_count != undefined && data.total_count > 0) {
+						var table = '';
+						table += '<table class="table data-table table-striped table-bordered">';
+						table += '<thead><tr><th class="thc-1">&nbsp;</th><th class="thc-2">Product</th><th class="thc-3">Price</th><th class="thc-4">Quantity</th><th class="thc-5">Sum</th><th class="thc-6">&nbsp;</th></tr></thead>';
+						table += '<tbody>';
+						for(var i in data.products) {
+							table += '<tr>';
+							table += '<td class="thc-1"><img src="' + data.products[i].image + '" width="50" height="50" /></td>';
+							table += '<td class="thc-2"><a href="' + data.products[i].link + '">' + data.products[i].name + '</a></td>';
+							table += '<td class="thc-3">' + data.products[i].price + ' ' + data.currency.code + '</td>';
+							table += '<td class="thc-4"><button type="button" class="btn btn-minus" onclick="frontend.ShopBasketProductMinus(this,' + data.products[i].id + ');"><span>-</span></button><input class="form-control" type="text" value="' + data.products[i].quantity + '" readonly><button type="button" class="btn btn-plus" onclick="frontend.ShopBasketProductPlus(this,' + data.products[i].id + ');"><span>+</span></button></td>';
+							table += '<td class="thc-5">' + data.products[i].sum + ' ' + data.currency.code + '</td>';
+							table += '<td class="thc-6"><a href="" onclick="frontend.ShopBasketProductRemove(this,' + data.products[i].id + ');return false;">&times;</a></td>';
+							table += '</tr>';
+						}
+						table += '</tbody>';
+						table += '</table>';
+						table += '<div class="total"><span class="caption">Total sum:</span><span class="value">' + data.total_sum + ' ' + data.currency.code + '</span></div>';
+						if(success) { success(table, data.total_count); }
+					} else {
+						if(success) { success('You basket currently empty...', 0); }
+					}
+				} else {
+					window.location.reload(true);
+				}
+			}, function(xhr, status, error) {
+				if(fail) { fail(xhr, status, error); }
+			}, function() {
+				if(always) { always(); }
+			});
+		};
+
+		function ShopBasketEnableDisableOrderBtn(total) {
+			$('#sys-modal-shop-basket button.btn-order').prop('disabled', total <= 0);
+		};
+
 		function Initialize() {
 			// Check if jQuery was loaded
 			if(typeof $ == 'function') {
 				ShopProductsInitLightGallery();
-				// ShopBasketAjaxUpdateCount();
 			} else {
 				console.log('Error: jQuery is not loaded!');
 			}
@@ -105,13 +156,16 @@ var VarScriptsJsFile = []byte(`(function(window, $) {
 
 		// Public
 		return {
+			ShopBasketBtnCollapse: function() {
+				if(!$('.navbar-toggler').hasClass('collapsed')) {
+					$('.navbar-toggler').click();
+				}
+				return true;
+			},
+
 			ShopBasketOpen: function(object) {
 				if(ShopBasketObjectIsNotBlocked(object)) {
 					ShopBasketBlockObject(object);
-
-					// ShopBasketSetNavBtnProductsCount(0);
-					// console.log('ShopOpenBasket', object);
-					// --------------------------------------------------
 					var html = '<div class="modal fade" id="sys-modal-shop-basket" tabindex="-1" role="dialog" aria-labelledby="sysModalShopBasketLabel" aria-hidden="true"> \
 						<div class="modal-dialog modal-dialog-centered" role="document"> \
 							<div class="modal-content"> \
@@ -122,16 +176,13 @@ var VarScriptsJsFile = []byte(`(function(window, $) {
 										<span aria-hidden="true">&times;</span> \
 									</button> \
 								</div> \
-								<div class="modal-body text-left"> \
-									<div class="form-group"> \
-										<input type="text" class="form-control" name="product-name" value="" placeholder="Type product name here..." readonly autocomplete="off"> \
-									</div> \
-									<div class="form-group" style="margin-bottom:0px;"> \
-										<div class="products-list"></div> \
-									</div> \
+								<div class="modal-body text-left" style="position:relative;"> \
+									<div class="blocker" style="position:absolute;left:0px;top:0px;width:100%;height:100%;background:#fff;opacity:0.5;display:none;"></div> \
+									<div class="data"></div> \
 								</div> \
 								<div class="modal-footer"> \
-									<button type="button" class="btn btn-secondary" data-dismiss="modal">Continue shopping</button> \
+									<button type="button" class="btn btn-close btn-secondary" data-dismiss="modal">Continue Shopping</button> \
+									<button type="button" class="btn btn-order btn-success" disabled>Make order</button> \
 								</div> \
 							</div> \
 						</div> \
@@ -145,8 +196,14 @@ var VarScriptsJsFile = []byte(`(function(window, $) {
 					$('#sys-modal-shop-basket').on('hidden.bs.modal', function(e) {
 						$('#sys-modal-shop-basket-placeholder').html('');
 					});
-					$("#sys-modal-shop-basket").modal('show');
-					// --------------------------------------------------
+
+					ShopBasketAjaxProductsHtml(function(html, total) {
+						$('#sys-modal-shop-basket .modal-body .data').html(html);
+						ShopBasketEnableDisableOrderBtn(total);
+						$("#sys-modal-shop-basket").modal('show');
+					}, function(xhr, status, error) {
+						window.location.reload(true);
+					});
 
 					ShopBasketUnBlockObject(object);
 				}
@@ -159,8 +216,7 @@ var VarScriptsJsFile = []byte(`(function(window, $) {
 					ShopBasketAjaxCommand('plus', product_id, function(data) {
 						frontend.ShopBasketOpen();
 					}, function(xhr, status, error) {
-						// console.log('fail', xhr, status, error, product_id);
-						// Page reload
+						window.location.reload(true);
 					}, function() {
 						ShopBasketAjaxUpdateCount();
 						ShopBasketUnBlockObject(object);
@@ -172,15 +228,20 @@ var VarScriptsJsFile = []byte(`(function(window, $) {
 			ShopBasketProductPlus: function(object, product_id) {
 				if(ShopBasketObjectIsNotBlocked(object)) {
 					ShopBasketBlockObject(object);
+					$('#sys-modal-shop-basket .modal-body .blocker').css('display', 'block');
 					ShopBasketAjaxCommand('plus', product_id, function(data) {
-						// console.log('success', data, product_id);
-						// Update popup content
+						ShopBasketAjaxProductsHtml(function(html, total) {
+							$('#sys-modal-shop-basket .modal-body .data').html(html);
+							ShopBasketEnableDisableOrderBtn(total);
+						}, function(xhr, status, error) {
+							window.location.reload(true);
+						});
 					}, function(xhr, status, error) {
-						// console.log('fail', xhr, status, error, product_id);
-						// Page reload
+						window.location.reload(true);
 					}, function() {
 						ShopBasketAjaxUpdateCount();
 						ShopBasketUnBlockObject(object);
+						$('#sys-modal-shop-basket .modal-body .blocker').css('display', 'none');
 					});
 				}
 				return false;
@@ -189,15 +250,20 @@ var VarScriptsJsFile = []byte(`(function(window, $) {
 			ShopBasketProductMinus: function(object, product_id) {
 				if(ShopBasketObjectIsNotBlocked(object)) {
 					ShopBasketBlockObject(object);
+					$('#sys-modal-shop-basket .modal-body .blocker').css('display', 'block');
 					ShopBasketAjaxCommand('minus', product_id, function(data) {
-						// console.log('success', data, product_id);
-						// Update popup content
+						ShopBasketAjaxProductsHtml(function(html, total) {
+							$('#sys-modal-shop-basket .modal-body .data').html(html);
+							ShopBasketEnableDisableOrderBtn(total);
+						}, function(xhr, status, error) {
+							window.location.reload(true);
+						});
 					}, function(xhr, status, error) {
-						// console.log('fail', xhr, status, error, product_id);
-						// Page reload
+						window.location.reload(true);
 					}, function() {
 						ShopBasketAjaxUpdateCount();
 						ShopBasketUnBlockObject(object);
+						$('#sys-modal-shop-basket .modal-body .blocker').css('display', 'none');
 					});
 				}
 				return false;
@@ -206,15 +272,20 @@ var VarScriptsJsFile = []byte(`(function(window, $) {
 			ShopBasketProductRemove: function(object, product_id) {
 				if(ShopBasketObjectIsNotBlocked(object)) {
 					ShopBasketBlockObject(object);
+					$('#sys-modal-shop-basket .modal-body .blocker').css('display', 'block');
 					ShopBasketAjaxCommand('remove', product_id, function(data) {
-						// console.log('success', data, product_id);
-						// Update popup content
+						ShopBasketAjaxProductsHtml(function(html, total) {
+							$('#sys-modal-shop-basket .modal-body .data').html(html);
+							ShopBasketEnableDisableOrderBtn(total);
+						}, function(xhr, status, error) {
+							window.location.reload(true);
+						});
 					}, function(xhr, status, error) {
-						// console.log('fail', xhr, status, error, product_id);
-						// Page reload
+						window.location.reload(true);
 					}, function() {
 						ShopBasketAjaxUpdateCount();
 						ShopBasketUnBlockObject(object);
+						$('#sys-modal-shop-basket .modal-body .blocker').css('display', 'none');
 					});
 				}
 				return false;

+ 1 - 1
assets/template/shop_category_html_file.go

@@ -28,7 +28,7 @@ var VarShopCategoryHtmlFile = []byte(`{{template "header.html" .}}
 						<div class="card-text">{{.Briefly}}</div>
 					</div>
 					<div class="card-footer">
-						<span class="price">{{.PriceFormat "%.2f"}} {{.Currency.Code}}</span><a href="{{.Permalink}}" class="btn btn-success" onclick="window&&window.frontend&&frontend.ShopBasketProductAdd(this, {{.Id}});return false;">Buy</a>
+						<span class="price">{{.PriceFormat "%.2f"}} {{$.Data.Shop.CurrentCurrency.Code}}</span><a href="{{.Permalink}}" class="btn btn-success" onclick="window&&window.frontend&&frontend.ShopBasketProductAdd(this, {{.Id}});return false;">Buy</a>
 					</div>
 				</div>
 			{{end}}

+ 1 - 1
assets/template/shop_html_file.go

@@ -28,7 +28,7 @@ var VarShopHtmlFile = []byte(`{{template "header.html" .}}
 						<div class="card-text">{{.Briefly}}</div>
 					</div>
 					<div class="card-footer">
-						<span class="price">{{.PriceFormat "%.2f"}} {{.Currency.Code}}</span><a href="{{.Permalink}}" class="btn btn-success" onclick="window&&window.frontend&&frontend.ShopBasketProductAdd(this, {{.Id}});return false;">Buy</a>
+						<span class="price">{{.PriceFormat "%.2f"}} {{$.Data.Shop.CurrentCurrency.Code}}</span><a href="{{.Permalink}}" class="btn btn-success" onclick="window&&window.frontend&&frontend.ShopBasketProductAdd(this, {{.Id}});return false;">Buy</a>
 					</div>
 				</div>
 			{{end}}

+ 2 - 2
assets/template/shop_product_html_file.go

@@ -49,7 +49,7 @@ var VarShopProductHtmlFile = []byte(`{{template "header.html" .}}
 						{{end}}
 						<div class="card mt-3{{if not $.Data.Shop.Product.HaveVariations}} mt-sm-3 mt-md-0 mt-lg-0{{end}}">
 							<div class="card-body">
-								<h3 class="price mb-0 mr-4">{{$.Data.Shop.Product.PriceFormat "%.2f"}} {{$.Data.Shop.Product.Currency.Code}}</h3><a href="" class="btn btn-success btn-buy" onclick="window&&window.frontend&&frontend.ShopBasketProductAdd(this, {{$.Data.Shop.Product.Id}});return false;">Buy</a>
+								<h3 class="price mb-0 mr-4">{{$.Data.Shop.Product.PriceFormat "%.2f"}} {{$.Data.Shop.CurrentCurrency.Code}}</h3><a href="" class="btn btn-success btn-buy" onclick="window&&window.frontend&&frontend.ShopBasketProductAdd(this, {{$.Data.Shop.Product.Id}});return false;">Buy</a>
 							</div>
 						</div>
 						<div class="card mt-3">
@@ -117,7 +117,7 @@ var VarShopProductHtmlFile = []byte(`{{template "header.html" .}}
 							</div>
 							<div class="card mt-3">
 								<div class="card-body">
-									<h3 class="price mb-0 mr-4">{{$.Data.Shop.Product.PriceFormat "%.2f"}} {{$.Data.Shop.Product.Currency.Code}}</h3><a href="" class="btn btn-success btn-buy" onclick="window&&window.frontend&&frontend.ShopBasketProductAdd(this, {{$.Data.Shop.Product.Id}});return false;">Buy</a>
+									<h3 class="price mb-0 mr-4">{{$.Data.Shop.Product.PriceFormat "%.2f"}} {{$.Data.Shop.CurrentCurrency.Code}}</h3><a href="" class="btn btn-success btn-buy" onclick="window&&window.frontend&&frontend.ShopBasketProductAdd(this, {{$.Data.Shop.Product.Id}});return false;">Buy</a>
 								</div>
 							</div>
 						</div>

+ 139 - 1
assets/template/styles_css_file.go

@@ -27,7 +27,6 @@ var VarStylesCssFile = []byte(`/* Fix bootstrap */
 @media (min-width: 1200px) {
 	.container {
 		 max-width: 1000px; 
-		/*background: red;*/
 	}
 
 	.navbar-expand-lg .navbar-nav {
@@ -287,4 +286,143 @@ footer {
 
 .fixed-top-bar .navbar.navbar-cats {
 	position: static;
+}
+
+/* Shop basket */
+#sys-modal-shop-basket .data .table tbody td {
+	vertical-align: middle;
+}
+
+#sys-modal-shop-basket .data .table .thc-1 {
+	width: 75px;
+}
+
+#sys-modal-shop-basket .data .table .thc-4 {
+	width: 180px;
+	text-align: center;
+}
+
+#sys-modal-shop-basket .data .table .thc-4 .btn {
+	width: 40px;
+}
+
+#sys-modal-shop-basket .data .table .thc-4 .btn-minus {
+	float: left;
+}
+
+#sys-modal-shop-basket .data .table .thc-4 .btn-plus {
+	float: right;
+}
+
+#sys-modal-shop-basket .data .table .thc-4 .form-control {
+	width: auto;
+	display: inline-block;
+	text-align: center;
+	width: 60px;
+}
+
+#sys-modal-shop-basket .data .table .thc-5 {
+	width: 110px;
+}
+
+#sys-modal-shop-basket .data .table tbody .thc-6 {
+	width: 40px;
+	font-size: 1.5rem;
+	font-weight: bold;
+	text-align: right;
+	vertical-align: middle;
+}
+
+#sys-modal-shop-basket .data .table tbody .thc-6 a:hover {
+	text-decoration: none;
+}
+
+#sys-modal-shop-basket .data .total {
+	text-align: right;
+	font-size: 1.5rem;
+}
+
+#sys-modal-shop-basket .data .total span {
+	display: inline-block;
+}
+
+#sys-modal-shop-basket .data .total span.caption {
+	margin-right: 1rem;
+}
+
+@media (max-width: 768px) {
+	#sys-modal-shop-basket .data .table td {
+		display: block;
+	}
+
+	#sys-modal-shop-basket .data .table .thc-3 {
+		display: none;
+	}
+
+	#sys-modal-shop-basket .data .table thead {
+		display: none;
+	}
+
+	#sys-modal-shop-basket .data .table .thc-1 {
+		width: auto;
+	}
+
+	#sys-modal-shop-basket .data .table .thc-1 img {
+		width: 100%;
+		height: auto;
+	}
+
+	#sys-modal-shop-basket .data .table .thc-4,
+	#sys-modal-shop-basket .data .table .thc-5,
+	#sys-modal-shop-basket .data .table tbody .thc-6 {
+		width: auto;
+	}
+
+	#sys-modal-shop-basket .data .table tbody .thc-6 {
+		text-align: left;
+	}
+
+	#sys-modal-shop-basket .modal-footer {
+		display: block;
+		text-align: right;
+	}
+
+	#sys-modal-shop-basket .modal-footer>:not(:last-child) {
+		margin-right: 0px;
+	}
+
+	#sys-modal-shop-basket .modal-footer>:not(:first-child) {
+		margin-left: 0px;
+		margin-top: 1rem;
+	}
+}
+
+@media (min-width: 768px) {
+	#sys-modal-shop-basket .modal-dialog {
+		max-width: 660px
+	}
+
+	#sys-modal-shop-basket .data .table .thc-3 {
+		display: none;
+	}
+}
+
+@media (min-width: 992px) {
+	#sys-modal-shop-basket .modal-dialog {
+		max-width: 900px
+	}
+
+	#sys-modal-shop-basket .data .table .thc-3 {
+		display: table-cell;
+	}
+}
+
+@media (min-width: 1200px) {
+	#sys-modal-shop-basket .modal-dialog {
+		max-width: 940px
+	}
+
+	#sys-modal-shop-basket .data .table .thc-3 {
+		display: table-cell;
+	}
 }`)

+ 13 - 6
engine/basket/basket.go

@@ -1,6 +1,7 @@
 package basket
 
 import (
+	"net/http"
 	"sync"
 
 	"golang-fave/engine/sqlw"
@@ -17,7 +18,7 @@ func New() *Basket {
 	return &b
 }
 
-func (this *Basket) Info(host, session_id string, db *sqlw.DB, currency_id int) string {
+func (this *Basket) Info(r *http.Request, host, session_id string, db *sqlw.DB, currency_id int) string {
 	if host == "" || session_id == "" {
 		return (&dResponse{IsError: true, Msg: "basket_host_or_session_not_set", Message: ""}).String()
 	}
@@ -28,7 +29,8 @@ func (this *Basket) Info(host, session_id string, db *sqlw.DB, currency_id int)
 	defer this.Unlock()
 	if h, ok := this.hosts[host]; ok == true {
 		if s, ok := h.sessions[session_id]; ok == true {
-			return s.String()
+			s.Preload(r, db)
+			return s.String(db)
 		} else {
 			return (&dResponse{IsError: false, Msg: "basket_is_empty", Message: ""}).String()
 		}
@@ -37,7 +39,7 @@ func (this *Basket) Info(host, session_id string, db *sqlw.DB, currency_id int)
 	}
 }
 
-func (this *Basket) Plus(host, session_id string, db *sqlw.DB, product_id int) string {
+func (this *Basket) Plus(r *http.Request, host, session_id string, db *sqlw.DB, product_id int) string {
 	if host == "" || session_id == "" {
 		return (&dResponse{IsError: true, Msg: "basket_host_or_session_not_set", Message: ""}).String()
 	}
@@ -47,11 +49,14 @@ func (this *Basket) Plus(host, session_id string, db *sqlw.DB, product_id int) s
 
 	if h, ok := this.hosts[host]; ok == true {
 		if s, ok := h.sessions[session_id]; ok == true {
+			s.Preload(r, db)
 			s.Plus(db, product_id)
 		}
 	} else {
 		s := &session{}
+		s.listCurrencies = map[int]*currency{}
 		s.Products = map[int]*product{}
+		s.Preload(r, db)
 		s.Plus(db, product_id)
 		h := &onehost{}
 		h.sessions = map[string]*session{}
@@ -62,7 +67,7 @@ func (this *Basket) Plus(host, session_id string, db *sqlw.DB, product_id int) s
 	return (&dResponse{IsError: false, Msg: "basket_product_plus", Message: ""}).String()
 }
 
-func (this *Basket) Minus(host, session_id string, db *sqlw.DB, product_id int) string {
+func (this *Basket) Minus(r *http.Request, host, session_id string, db *sqlw.DB, product_id int) string {
 	if host == "" || session_id == "" {
 		return (&dResponse{IsError: true, Msg: "basket_host_or_session_not_set", Message: ""}).String()
 	}
@@ -72,6 +77,7 @@ func (this *Basket) Minus(host, session_id string, db *sqlw.DB, product_id int)
 
 	if h, ok := this.hosts[host]; ok == true {
 		if s, ok := h.sessions[session_id]; ok == true {
+			s.Preload(r, db)
 			s.Minus(db, product_id)
 		}
 	}
@@ -79,7 +85,7 @@ func (this *Basket) Minus(host, session_id string, db *sqlw.DB, product_id int)
 	return (&dResponse{IsError: false, Msg: "basket_product_minus", Message: ""}).String()
 }
 
-func (this *Basket) Remove(host, session_id string, db *sqlw.DB, product_id int) string {
+func (this *Basket) Remove(r *http.Request, host, session_id string, db *sqlw.DB, product_id int) string {
 	if host == "" || session_id == "" {
 		return (&dResponse{IsError: true, Msg: "basket_host_or_session_not_set", Message: ""}).String()
 	}
@@ -89,6 +95,7 @@ func (this *Basket) Remove(host, session_id string, db *sqlw.DB, product_id int)
 
 	if h, ok := this.hosts[host]; ok == true {
 		if s, ok := h.sessions[session_id]; ok == true {
+			s.Preload(r, db)
 			s.Remove(db, product_id)
 		}
 	}
@@ -96,7 +103,7 @@ func (this *Basket) Remove(host, session_id string, db *sqlw.DB, product_id int)
 	return (&dResponse{IsError: false, Msg: "basket_product_remove", Message: ""}).String()
 }
 
-func (this *Basket) ProductsCount(host, session_id string) int {
+func (this *Basket) ProductsCount(r *http.Request, host, session_id string) int {
 	if host != "" && session_id != "" {
 		this.Lock()
 		defer this.Unlock()

+ 10 - 6
engine/basket/product.go

@@ -1,10 +1,14 @@
 package basket
 
 type product struct {
-	Id       int     `json:"id"`
-	Name     string  `json:"name"`
-	Image    string  `json:"image"`
-	Price    float64 `json:"price"`
-	Quantity int     `json:"quantity"`
-	Sum      float64 `json:"sum"`
+	currency *currency
+	price    float64
+
+	Id       int    `json:"id"`
+	Name     string `json:"name"`
+	Image    string `json:"image"`
+	Link     string `json:"link"`
+	Price    string `json:"price"`
+	Quantity int    `json:"quantity"`
+	Sum      string `json:"sum"`
 }

+ 185 - 17
engine/basket/session.go

@@ -3,41 +3,208 @@ package basket
 import (
 	"encoding/json"
 	"html"
+	"net/http"
+	"strings"
 
 	"golang-fave/engine/sqlw"
 	"golang-fave/utils"
 )
 
 type session struct {
+	listCurrencies map[int]*currency
+	totalSum       float64
+
 	Products   map[int]*product `json:"products"`
 	Currency   *currency        `json:"currency"`
-	TotalSum   float64          `json:"total_sum"`
+	TotalSum   string           `json:"total_sum"`
 	TotalCount int              `json:"total_count"`
 }
 
-func (this *session) String() string {
+func (this *session) makePrice(product_price float64, product_currency_id int) float64 {
+	if this.Currency == nil {
+		return product_price
+	}
+	if this.Currency.Id == product_currency_id {
+		return product_price
+	}
+	if product_currency_id == 1 {
+		return product_price * this.Currency.Coefficient
+	} else {
+		if c, ok := this.listCurrencies[product_currency_id]; ok == true {
+			return product_price / c.Coefficient
+		} else {
+			return product_price
+		}
+	}
+}
+
+func (this *session) updateProducts(db *sqlw.DB) {
+	products_ids := []int{}
+	for _, product := range this.Products {
+		products_ids = append(products_ids, product.Id)
+	}
+	if len(products_ids) > 0 {
+		if rows, err := db.Query(
+			`SELECT
+				shop_products.id,
+				shop_products.name,
+				shop_products.price,
+				shop_products.alias,
+				shop_currencies.id,
+				shop_currencies.name,
+				shop_currencies.coefficient,
+				shop_currencies.code,
+				shop_currencies.symbol
+			FROM
+				shop_products
+				LEFT JOIN shop_currencies ON shop_currencies.id = shop_products.currency
+			WHERE
+				shop_products.active = 1 AND
+				shop_products.id IN (` + strings.Join(utils.ArrayOfIntToArrayOfString(products_ids), ",") + `)
+			LIMIT 1;`,
+		); err == nil {
+			defer rows.Close()
+			for rows.Next() {
+				row := &utils.MySql_shop_product{}
+				roc := &utils.MySql_shop_currency{}
+				if err = rows.Scan(
+					&row.A_id,
+					&row.A_name,
+					&row.A_price,
+					&row.A_alias,
+					&roc.A_id,
+					&roc.A_name,
+					&roc.A_coefficient,
+					&roc.A_code,
+					&roc.A_symbol,
+				); err == nil {
+					if p, ok := this.Products[row.A_id]; ok == true {
+						p.Name = html.EscapeString(row.A_name)
+						p.Image = "/products/images/1/thumb-0-1570673803.jpg"
+						p.Link = "/shop/" + row.A_alias + "/"
+						p.price = row.A_price
+						p.currency.Id = roc.A_id
+						p.currency.Name = html.EscapeString(roc.A_name)
+						p.currency.Coefficient = roc.A_coefficient
+						p.currency.Code = html.EscapeString(roc.A_code)
+						p.currency.Symbol = html.EscapeString(roc.A_symbol)
+					}
+				}
+			}
+		}
+	}
+}
+
+func (this *session) updateTotals() {
+	this.totalSum = 0
+	this.TotalCount = 0
+	for _, product := range this.Products {
+		product.Price = utils.Float64ToStrF(this.makePrice(product.price, product.currency.Id), "%.2f")
+		product.Sum = utils.Float64ToStrF(this.makePrice(product.price*float64(product.Quantity), product.currency.Id), "%.2f")
+		this.totalSum += this.makePrice(product.price, product.currency.Id) * float64(product.Quantity)
+		this.TotalCount += product.Quantity
+	}
+	this.TotalSum = utils.Float64ToStrF(this.totalSum, "%.2f")
+}
+
+// Info, Plus, Minus
+func (this *session) Preload(r *http.Request, db *sqlw.DB) {
+	user_currency := 1
+
+	if cookie, err := r.Cookie("currency"); err == nil {
+		user_currency = utils.StrToInt(cookie.Value)
+	}
+
+	// Clear list of currencies
+	this.listCurrencies = map[int]*currency{}
+
+	// Load currencies from database
+	if rows, err := db.Query(
+		`SELECT
+			id,
+			name,
+			coefficient,
+			code,
+			symbol
+		FROM
+			shop_currencies
+		ORDER BY
+			id ASC
+		;`,
+	); err == nil {
+		defer rows.Close()
+		for rows.Next() {
+			roc := &utils.MySql_shop_currency{}
+			if err = rows.Scan(
+				&roc.A_id,
+				&roc.A_name,
+				&roc.A_coefficient,
+				&roc.A_code,
+				&roc.A_symbol,
+			); err == nil {
+				this.listCurrencies[roc.A_id] = &currency{
+					Id:          roc.A_id,
+					Name:        html.EscapeString(roc.A_name),
+					Coefficient: roc.A_coefficient,
+					Code:        html.EscapeString(roc.A_code),
+					Symbol:      html.EscapeString(roc.A_symbol),
+				}
+			}
+		}
+	}
+
+	// Check if selected currency is exists
+	if _, ok := this.listCurrencies[user_currency]; ok != true {
+		user_currency = 1
+	}
+
+	// Save selected currency
+	if c, ok := this.listCurrencies[user_currency]; ok == true {
+		this.Currency = &currency{
+			Id:          c.Id,
+			Name:        c.Name,
+			Coefficient: c.Coefficient,
+			Code:        c.Code,
+			Symbol:      c.Symbol,
+		}
+	}
+}
+
+func (this *session) String(db *sqlw.DB) string {
+	this.updateProducts(db)
+	this.updateTotals()
+
 	json, err := json.Marshal(this)
 	if err != nil {
 		return `{"msg":"basket_engine_error","message":"` + err.Error() + `"}`
 	}
+
 	return string(json)
 }
 
 func (this *session) Plus(db *sqlw.DB, product_id int) {
 	if p, ok := this.Products[product_id]; ok == true {
 		p.Quantity++
-		p.Sum = p.Price * float64(p.Quantity)
+		this.updateProducts(db)
 		this.updateTotals()
 		return
 	}
 	row := &utils.MySql_shop_product{}
+	roc := &utils.MySql_shop_currency{}
 	if err := db.QueryRow(`
 		SELECT
 			shop_products.id,
 			shop_products.name,
-			shop_products.price
+			shop_products.price,
+			shop_products.alias,
+			shop_currencies.id,
+			shop_currencies.name,
+			shop_currencies.coefficient,
+			shop_currencies.code,
+			shop_currencies.symbol
 		FROM
 			shop_products
+			LEFT JOIN shop_currencies ON shop_currencies.id = shop_products.currency
 		WHERE
 			shop_products.active = 1 AND
 			shop_products.id = ?
@@ -47,16 +214,25 @@ func (this *session) Plus(db *sqlw.DB, product_id int) {
 		&row.A_id,
 		&row.A_name,
 		&row.A_price,
+		&row.A_alias,
+		&roc.A_id,
+		&roc.A_name,
+		&roc.A_coefficient,
+		&roc.A_code,
+		&roc.A_symbol,
 	); err == nil {
 		// Load product image here
 		this.Products[product_id] = &product{
+			currency: &currency{Id: roc.A_id, Name: roc.A_name, Coefficient: roc.A_coefficient, Code: roc.A_code, Symbol: roc.A_symbol},
+
 			Id:       row.A_id,
 			Name:     html.EscapeString(row.A_name),
-			Image:    "",
-			Price:    row.A_price,
+			Image:    "/products/images/1/thumb-0-1570673803.jpg",
+			Link:     "/shop/" + row.A_alias + "/",
+			price:    row.A_price,
 			Quantity: 1,
-			Sum:      row.A_price,
 		}
+		this.updateProducts(db)
 		this.updateTotals()
 	}
 }
@@ -65,10 +241,10 @@ func (this *session) Minus(db *sqlw.DB, product_id int) {
 	if p, ok := this.Products[product_id]; ok == true {
 		if p.Quantity > 1 {
 			p.Quantity--
-			p.Sum = p.Price * float64(p.Quantity)
 		} else {
 			delete(this.Products, product_id)
 		}
+		this.updateProducts(db)
 		this.updateTotals()
 	}
 }
@@ -76,6 +252,7 @@ func (this *session) Minus(db *sqlw.DB, product_id int) {
 func (this *session) Remove(db *sqlw.DB, product_id int) {
 	if _, ok := this.Products[product_id]; ok == true {
 		delete(this.Products, product_id)
+		this.updateProducts(db)
 		this.updateTotals()
 	}
 }
@@ -83,12 +260,3 @@ func (this *session) Remove(db *sqlw.DB, product_id int) {
 func (this *session) ProductsCount() int {
 	return this.TotalCount
 }
-
-func (this *session) updateTotals() {
-	this.TotalSum = 0
-	this.TotalCount = 0
-	for _, product := range this.Products {
-		this.TotalSum += product.Price * float64(product.Quantity)
-		this.TotalCount += product.Quantity
-	}
-}

+ 1 - 1
engine/fetdata/fetdata.go

@@ -199,5 +199,5 @@ func (this *FERData) CachedBlock5() template.HTML {
 }
 
 func (this *FERData) ShopBasketProductsCount() int {
-	return this.wrap.ShopBasket.ProductsCount(this.wrap.CurrHost, this.wrap.GetSessionId())
+	return this.wrap.ShopBasket.ProductsCount(this.wrap.R, this.wrap.CurrHost, this.wrap.GetSessionId())
 }

+ 17 - 0
engine/fetdata/shop.go

@@ -663,6 +663,23 @@ func (this *Shop) PaginationNext() *ShopPagination {
 	return this.paginationNext
 }
 
+func (this *Shop) Currencies() []*ShopCurrency {
+	result := []*ShopCurrency{}
+	for _, currency := range *this.wrap.ShopGetAllCurrencies() {
+		obj := currency
+		result = append(result, (&ShopCurrency{wrap: this.wrap, object: &obj}).load())
+	}
+
+	sort.Slice(result, func(i, j int) bool { return result[i].Id() < result[j].Id() })
+
+	return result
+}
+
+func (this *Shop) CurrentCurrency() *ShopCurrency {
+	obj := *this.wrap.ShopGetCurrentCurrency()
+	return (&ShopCurrency{wrap: this.wrap, object: &obj}).load()
+}
+
 func (this *Shop) Categories(parent, depth int) []*ShopCategory {
 	this.preload_cats()
 

+ 18 - 3
engine/fetdata/shop_product.go

@@ -217,9 +217,24 @@ func (this *ShopProduct) Price() float64 {
 	if this == nil {
 		return 0
 	}
-	// TODO: read currency from session?
-	// this.object.A_price * this.Currency().Coefficient()
-	return this.object.A_price
+	if this.Currency() == nil {
+		return this.object.A_price
+	}
+	if this.wrap.ShopGetCurrentCurrency() == nil {
+		return this.object.A_price
+	}
+	if this.wrap.ShopGetCurrentCurrency().A_id == this.Currency().Id() {
+		return this.object.A_price
+	}
+	if this.Currency().Id() == 1 {
+		return this.object.A_price * this.wrap.ShopGetCurrentCurrency().A_coefficient
+	} else {
+		if c, ok := (*this.wrap.ShopGetAllCurrencies())[this.Currency().Id()]; ok == true {
+			return this.object.A_price / c.A_coefficient
+		} else {
+			return this.object.A_price
+		}
+	}
 }
 
 func (this *ShopProduct) PriceFormat(format string) string {

+ 50 - 0
engine/wrapper/wrapper.go

@@ -56,6 +56,8 @@ type Wrapper struct {
 
 	DB   *sqlw.DB
 	User *utils.MySql_user
+
+	ShopAllCurrencies *map[int]utils.MySql_shop_currency
 }
 
 func New(l *logger.Logger, w http.ResponseWriter, r *http.Request, s *session.Session, c *cblocks.CacheBlocks, host, port, chost, dirConfig, dirHtdocs, dirLogs, dirTemplate, dirTmp string, mp *mysqlpool.MySqlPool, sb *basket.Basket) *Wrapper {
@@ -346,3 +348,51 @@ func (this *Wrapper) RemoveProductImageThumbnails(product_id, filename string) e
 	}
 	return this.RecreateProductImgFiles()
 }
+
+func (this *Wrapper) ShopGetAllCurrencies() *map[int]utils.MySql_shop_currency {
+	if this.ShopAllCurrencies == nil {
+		this.ShopAllCurrencies = &map[int]utils.MySql_shop_currency{}
+		if rows, err := this.DB.Query(
+			`SELECT
+				id,
+				name,
+				coefficient,
+				code,
+				symbol
+			FROM
+				shop_currencies
+			ORDER BY
+				id ASC
+			;`,
+		); err == nil {
+			defer rows.Close()
+			for rows.Next() {
+				row := utils.MySql_shop_currency{}
+				if err = rows.Scan(
+					&row.A_id,
+					&row.A_name,
+					&row.A_coefficient,
+					&row.A_code,
+					&row.A_symbol,
+				); err == nil {
+					(*this.ShopAllCurrencies)[row.A_id] = row
+				}
+			}
+		}
+	}
+	return this.ShopAllCurrencies
+}
+
+func (this *Wrapper) ShopGetCurrentCurrency() *utils.MySql_shop_currency {
+	currency_id := 1
+	if cookie, err := this.R.Cookie("currency"); err == nil {
+		currency_id = utils.StrToInt(cookie.Value)
+	}
+	if _, ok := (*this.ShopGetAllCurrencies())[currency_id]; ok != true {
+		currency_id = 1
+	}
+	if p, ok := (*this.ShopGetAllCurrencies())[currency_id]; ok == true {
+		return &p
+	}
+	return nil
+}

+ 10 - 2
hosts/localhost/template/header.html

@@ -24,7 +24,8 @@
 			<nav class="navbar navbar-expand-lg navbar-light bg-light">
 				<div class="container">
 					<a class="navbar-brand" href="/">Fave {{$.System.InfoVersion}}</a>
-					<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
+					<button class="navbar-toggler collapsed" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
+						<span id="basket-mobile-btn" class="badge badge-pill badge-primary">{{$.Data.ShopBasketProductsCount}}</span>
 						<span class="navbar-toggler-icon"></span>
 					</button>
 					<div class="collapse navbar-collapse" id="navbarResponsive">
@@ -48,7 +49,14 @@
 								<a class="nav-link{{if eq $.Data.Module "404"}} active{{end}}" href="/not-existent-page/">404</a>
 							</li>
 							<li class="nav-item">
-								<a id="basket-nav-btn" class="nav-link" href="" onclick="window&&window.frontend&&frontend.ShopBasketOpen(this);return false;">Basket <span class="badge badge-pill badge-primary">{{$.Data.ShopBasketProductsCount}}</span></a>
+								<a id="basket-nav-btn" class="nav-link" href="" onclick="window&&window.frontend&&frontend.ShopBasketBtnCollapse()&&frontend.ShopBasketOpen(this);return false;">Basket <span class="badge badge-pill badge-primary">{{$.Data.ShopBasketProductsCount}}</span></a>
+							</li>
+							<li>
+								<select class="form-control" onchange="document.location.reload(true);">
+									{{range $.Data.Shop.Currencies}}
+										<option value="{{.Id}}"{{if eq .Id $.Data.Shop.CurrentCurrency.Id}} selected{{end}}>{{.Code}}</option>
+									{{end}}
+								</select>
 							</li>
 						</ul>
 					</div>

+ 102 - 31
hosts/localhost/template/scripts.js

@@ -46,6 +46,7 @@
 
 		function ShopBasketSetNavBtnProductsCount(value) {
 			$('#basket-nav-btn .badge').html(value);
+			$('#basket-mobile-btn').html(value);
 		};
 
 		function ShopBasketAjaxCommand(cmd, product_id, success, fail, always) {
@@ -62,13 +63,13 @@
 			});
 		};
 
-		function ShopBasketAjaxGetCount(success, fail, always) {
+		function ShopBasketAjaxGetInfo(success, fail, always) {
 			$.ajax({
 				type: "GET",
 				dataType: 'json',
 				url: '/shop/basket/info/'
 			}).done(function(data) {
-				if(success && data && data.total_count) { success(data.total_count); }
+				if(success && data) { success(data); }
 			}).fail(function(xhr, status, error) {
 				if(fail) { fail(xhr, status, error); }
 			}).always(function() {
@@ -76,17 +77,67 @@
 			});
 		};
 
+		function ShopBasketAjaxGetCount(success, fail, always) {
+			ShopBasketAjaxGetInfo(function(data) {
+				if(success && data && data.total_count != undefined) {
+					success(data.total_count);
+				}
+			}, function(xhr, status, error) {
+				if(fail) { fail(xhr, status, error); }
+			}, function() {
+				if(always) { always(); }
+			});
+		};
+
 		function ShopBasketAjaxUpdateCount() {
 			ShopBasketAjaxGetCount(function(count) {
 				ShopBasketSetNavBtnProductsCount(count);
 			});
 		};
 
+		function ShopBasketAjaxProductsHtml(success, fail, always) {
+			ShopBasketAjaxGetInfo(function(data) {
+				if(data) {
+					if(data.total_count != undefined && data.total_count > 0) {
+						var table = '';
+						table += '<table class="table data-table table-striped table-bordered">';
+						table += '<thead><tr><th class="thc-1">&nbsp;</th><th class="thc-2">Product</th><th class="thc-3">Price</th><th class="thc-4">Quantity</th><th class="thc-5">Sum</th><th class="thc-6">&nbsp;</th></tr></thead>';
+						table += '<tbody>';
+						for(var i in data.products) {
+							table += '<tr>';
+							table += '<td class="thc-1"><img src="' + data.products[i].image + '" width="50" height="50" /></td>';
+							table += '<td class="thc-2"><a href="' + data.products[i].link + '">' + data.products[i].name + '</a></td>';
+							table += '<td class="thc-3">' + data.products[i].price + ' ' + data.currency.code + '</td>';
+							table += '<td class="thc-4"><button type="button" class="btn btn-minus" onclick="frontend.ShopBasketProductMinus(this,' + data.products[i].id + ');"><span>-</span></button><input class="form-control" type="text" value="' + data.products[i].quantity + '" readonly><button type="button" class="btn btn-plus" onclick="frontend.ShopBasketProductPlus(this,' + data.products[i].id + ');"><span>+</span></button></td>';
+							table += '<td class="thc-5">' + data.products[i].sum + ' ' + data.currency.code + '</td>';
+							table += '<td class="thc-6"><a href="" onclick="frontend.ShopBasketProductRemove(this,' + data.products[i].id + ');return false;">&times;</a></td>';
+							table += '</tr>';
+						}
+						table += '</tbody>';
+						table += '</table>';
+						table += '<div class="total"><span class="caption">Total sum:</span><span class="value">' + data.total_sum + ' ' + data.currency.code + '</span></div>';
+						if(success) { success(table, data.total_count); }
+					} else {
+						if(success) { success('You basket currently empty...', 0); }
+					}
+				} else {
+					window.location.reload(true);
+				}
+			}, function(xhr, status, error) {
+				if(fail) { fail(xhr, status, error); }
+			}, function() {
+				if(always) { always(); }
+			});
+		};
+
+		function ShopBasketEnableDisableOrderBtn(total) {
+			$('#sys-modal-shop-basket button.btn-order').prop('disabled', total <= 0);
+		};
+
 		function Initialize() {
 			// Check if jQuery was loaded
 			if(typeof $ == 'function') {
 				ShopProductsInitLightGallery();
-				// ShopBasketAjaxUpdateCount();
 			} else {
 				console.log('Error: jQuery is not loaded!');
 			}
@@ -103,13 +154,16 @@
 
 		// Public
 		return {
+			ShopBasketBtnCollapse: function() {
+				if(!$('.navbar-toggler').hasClass('collapsed')) {
+					$('.navbar-toggler').click();
+				}
+				return true;
+			},
+
 			ShopBasketOpen: function(object) {
 				if(ShopBasketObjectIsNotBlocked(object)) {
 					ShopBasketBlockObject(object);
-
-					// ShopBasketSetNavBtnProductsCount(0);
-					// console.log('ShopOpenBasket', object);
-					// --------------------------------------------------
 					var html = '<div class="modal fade" id="sys-modal-shop-basket" tabindex="-1" role="dialog" aria-labelledby="sysModalShopBasketLabel" aria-hidden="true"> \
 						<div class="modal-dialog modal-dialog-centered" role="document"> \
 							<div class="modal-content"> \
@@ -120,16 +174,13 @@
 										<span aria-hidden="true">&times;</span> \
 									</button> \
 								</div> \
-								<div class="modal-body text-left"> \
-									<div class="form-group"> \
-										<input type="text" class="form-control" name="product-name" value="" placeholder="Type product name here..." readonly autocomplete="off"> \
-									</div> \
-									<div class="form-group" style="margin-bottom:0px;"> \
-										<div class="products-list"></div> \
-									</div> \
+								<div class="modal-body text-left" style="position:relative;"> \
+									<div class="blocker" style="position:absolute;left:0px;top:0px;width:100%;height:100%;background:#fff;opacity:0.5;display:none;"></div> \
+									<div class="data"></div> \
 								</div> \
 								<div class="modal-footer"> \
-									<button type="button" class="btn btn-secondary" data-dismiss="modal">Continue shopping</button> \
+									<button type="button" class="btn btn-close btn-secondary" data-dismiss="modal">Continue Shopping</button> \
+									<button type="button" class="btn btn-order btn-success" disabled>Make order</button> \
 								</div> \
 							</div> \
 						</div> \
@@ -143,8 +194,14 @@
 					$('#sys-modal-shop-basket').on('hidden.bs.modal', function(e) {
 						$('#sys-modal-shop-basket-placeholder').html('');
 					});
-					$("#sys-modal-shop-basket").modal('show');
-					// --------------------------------------------------
+
+					ShopBasketAjaxProductsHtml(function(html, total) {
+						$('#sys-modal-shop-basket .modal-body .data').html(html);
+						ShopBasketEnableDisableOrderBtn(total);
+						$("#sys-modal-shop-basket").modal('show');
+					}, function(xhr, status, error) {
+						window.location.reload(true);
+					});
 
 					ShopBasketUnBlockObject(object);
 				}
@@ -157,8 +214,7 @@
 					ShopBasketAjaxCommand('plus', product_id, function(data) {
 						frontend.ShopBasketOpen();
 					}, function(xhr, status, error) {
-						// console.log('fail', xhr, status, error, product_id);
-						// Page reload
+						window.location.reload(true);
 					}, function() {
 						ShopBasketAjaxUpdateCount();
 						ShopBasketUnBlockObject(object);
@@ -170,15 +226,20 @@
 			ShopBasketProductPlus: function(object, product_id) {
 				if(ShopBasketObjectIsNotBlocked(object)) {
 					ShopBasketBlockObject(object);
+					$('#sys-modal-shop-basket .modal-body .blocker').css('display', 'block');
 					ShopBasketAjaxCommand('plus', product_id, function(data) {
-						// console.log('success', data, product_id);
-						// Update popup content
+						ShopBasketAjaxProductsHtml(function(html, total) {
+							$('#sys-modal-shop-basket .modal-body .data').html(html);
+							ShopBasketEnableDisableOrderBtn(total);
+						}, function(xhr, status, error) {
+							window.location.reload(true);
+						});
 					}, function(xhr, status, error) {
-						// console.log('fail', xhr, status, error, product_id);
-						// Page reload
+						window.location.reload(true);
 					}, function() {
 						ShopBasketAjaxUpdateCount();
 						ShopBasketUnBlockObject(object);
+						$('#sys-modal-shop-basket .modal-body .blocker').css('display', 'none');
 					});
 				}
 				return false;
@@ -187,15 +248,20 @@
 			ShopBasketProductMinus: function(object, product_id) {
 				if(ShopBasketObjectIsNotBlocked(object)) {
 					ShopBasketBlockObject(object);
+					$('#sys-modal-shop-basket .modal-body .blocker').css('display', 'block');
 					ShopBasketAjaxCommand('minus', product_id, function(data) {
-						// console.log('success', data, product_id);
-						// Update popup content
+						ShopBasketAjaxProductsHtml(function(html, total) {
+							$('#sys-modal-shop-basket .modal-body .data').html(html);
+							ShopBasketEnableDisableOrderBtn(total);
+						}, function(xhr, status, error) {
+							window.location.reload(true);
+						});
 					}, function(xhr, status, error) {
-						// console.log('fail', xhr, status, error, product_id);
-						// Page reload
+						window.location.reload(true);
 					}, function() {
 						ShopBasketAjaxUpdateCount();
 						ShopBasketUnBlockObject(object);
+						$('#sys-modal-shop-basket .modal-body .blocker').css('display', 'none');
 					});
 				}
 				return false;
@@ -204,15 +270,20 @@
 			ShopBasketProductRemove: function(object, product_id) {
 				if(ShopBasketObjectIsNotBlocked(object)) {
 					ShopBasketBlockObject(object);
+					$('#sys-modal-shop-basket .modal-body .blocker').css('display', 'block');
 					ShopBasketAjaxCommand('remove', product_id, function(data) {
-						// console.log('success', data, product_id);
-						// Update popup content
+						ShopBasketAjaxProductsHtml(function(html, total) {
+							$('#sys-modal-shop-basket .modal-body .data').html(html);
+							ShopBasketEnableDisableOrderBtn(total);
+						}, function(xhr, status, error) {
+							window.location.reload(true);
+						});
 					}, function(xhr, status, error) {
-						// console.log('fail', xhr, status, error, product_id);
-						// Page reload
+						window.location.reload(true);
 					}, function() {
 						ShopBasketAjaxUpdateCount();
 						ShopBasketUnBlockObject(object);
+						$('#sys-modal-shop-basket .modal-body .blocker').css('display', 'none');
 					});
 				}
 				return false;

+ 1 - 1
hosts/localhost/template/shop-category.html

@@ -26,7 +26,7 @@
 						<div class="card-text">{{.Briefly}}</div>
 					</div>
 					<div class="card-footer">
-						<span class="price">{{.PriceFormat "%.2f"}} {{.Currency.Code}}</span><a href="{{.Permalink}}" class="btn btn-success" onclick="window&&window.frontend&&frontend.ShopBasketProductAdd(this, {{.Id}});return false;">Buy</a>
+						<span class="price">{{.PriceFormat "%.2f"}} {{$.Data.Shop.CurrentCurrency.Code}}</span><a href="{{.Permalink}}" class="btn btn-success" onclick="window&&window.frontend&&frontend.ShopBasketProductAdd(this, {{.Id}});return false;">Buy</a>
 					</div>
 				</div>
 			{{end}}

+ 2 - 2
hosts/localhost/template/shop-product.html

@@ -47,7 +47,7 @@
 						{{end}}
 						<div class="card mt-3{{if not $.Data.Shop.Product.HaveVariations}} mt-sm-3 mt-md-0 mt-lg-0{{end}}">
 							<div class="card-body">
-								<h3 class="price mb-0 mr-4">{{$.Data.Shop.Product.PriceFormat "%.2f"}} {{$.Data.Shop.Product.Currency.Code}}</h3><a href="" class="btn btn-success btn-buy" onclick="window&&window.frontend&&frontend.ShopBasketProductAdd(this, {{$.Data.Shop.Product.Id}});return false;">Buy</a>
+								<h3 class="price mb-0 mr-4">{{$.Data.Shop.Product.PriceFormat "%.2f"}} {{$.Data.Shop.CurrentCurrency.Code}}</h3><a href="" class="btn btn-success btn-buy" onclick="window&&window.frontend&&frontend.ShopBasketProductAdd(this, {{$.Data.Shop.Product.Id}});return false;">Buy</a>
 							</div>
 						</div>
 						<div class="card mt-3">
@@ -115,7 +115,7 @@
 							</div>
 							<div class="card mt-3">
 								<div class="card-body">
-									<h3 class="price mb-0 mr-4">{{$.Data.Shop.Product.PriceFormat "%.2f"}} {{$.Data.Shop.Product.Currency.Code}}</h3><a href="" class="btn btn-success btn-buy" onclick="window&&window.frontend&&frontend.ShopBasketProductAdd(this, {{$.Data.Shop.Product.Id}});return false;">Buy</a>
+									<h3 class="price mb-0 mr-4">{{$.Data.Shop.Product.PriceFormat "%.2f"}} {{$.Data.Shop.CurrentCurrency.Code}}</h3><a href="" class="btn btn-success btn-buy" onclick="window&&window.frontend&&frontend.ShopBasketProductAdd(this, {{$.Data.Shop.Product.Id}});return false;">Buy</a>
 								</div>
 							</div>
 						</div>

+ 1 - 1
hosts/localhost/template/shop.html

@@ -26,7 +26,7 @@
 						<div class="card-text">{{.Briefly}}</div>
 					</div>
 					<div class="card-footer">
-						<span class="price">{{.PriceFormat "%.2f"}} {{.Currency.Code}}</span><a href="{{.Permalink}}" class="btn btn-success" onclick="window&&window.frontend&&frontend.ShopBasketProductAdd(this, {{.Id}});return false;">Buy</a>
+						<span class="price">{{.PriceFormat "%.2f"}} {{$.Data.Shop.CurrentCurrency.Code}}</span><a href="{{.Permalink}}" class="btn btn-success" onclick="window&&window.frontend&&frontend.ShopBasketProductAdd(this, {{.Id}});return false;">Buy</a>
 					</div>
 				</div>
 			{{end}}

+ 140 - 2
hosts/localhost/template/styles.css

@@ -25,7 +25,6 @@
 @media (min-width: 1200px) {
 	.container {
 		 max-width: 1000px; 
-		/*background: red;*/
 	}
 
 	.navbar-expand-lg .navbar-nav {
@@ -285,4 +284,143 @@ footer {
 
 .fixed-top-bar .navbar.navbar-cats {
 	position: static;
-}
+}
+
+/* Shop basket */
+#sys-modal-shop-basket .data .table tbody td {
+	vertical-align: middle;
+}
+
+#sys-modal-shop-basket .data .table .thc-1 {
+	width: 75px;
+}
+
+#sys-modal-shop-basket .data .table .thc-4 {
+	width: 180px;
+	text-align: center;
+}
+
+#sys-modal-shop-basket .data .table .thc-4 .btn {
+	width: 40px;
+}
+
+#sys-modal-shop-basket .data .table .thc-4 .btn-minus {
+	float: left;
+}
+
+#sys-modal-shop-basket .data .table .thc-4 .btn-plus {
+	float: right;
+}
+
+#sys-modal-shop-basket .data .table .thc-4 .form-control {
+	width: auto;
+	display: inline-block;
+	text-align: center;
+	width: 60px;
+}
+
+#sys-modal-shop-basket .data .table .thc-5 {
+	width: 110px;
+}
+
+#sys-modal-shop-basket .data .table tbody .thc-6 {
+	width: 40px;
+	font-size: 1.5rem;
+	font-weight: bold;
+	text-align: right;
+	vertical-align: middle;
+}
+
+#sys-modal-shop-basket .data .table tbody .thc-6 a:hover {
+	text-decoration: none;
+}
+
+#sys-modal-shop-basket .data .total {
+	text-align: right;
+	font-size: 1.5rem;
+}
+
+#sys-modal-shop-basket .data .total span {
+	display: inline-block;
+}
+
+#sys-modal-shop-basket .data .total span.caption {
+	margin-right: 1rem;
+}
+
+@media (max-width: 768px) {
+	#sys-modal-shop-basket .data .table td {
+		display: block;
+	}
+
+	#sys-modal-shop-basket .data .table .thc-3 {
+		display: none;
+	}
+
+	#sys-modal-shop-basket .data .table thead {
+		display: none;
+	}
+
+	#sys-modal-shop-basket .data .table .thc-1 {
+		width: auto;
+	}
+
+	#sys-modal-shop-basket .data .table .thc-1 img {
+		width: 100%;
+		height: auto;
+	}
+
+	#sys-modal-shop-basket .data .table .thc-4,
+	#sys-modal-shop-basket .data .table .thc-5,
+	#sys-modal-shop-basket .data .table tbody .thc-6 {
+		width: auto;
+	}
+
+	#sys-modal-shop-basket .data .table tbody .thc-6 {
+		text-align: left;
+	}
+
+	#sys-modal-shop-basket .modal-footer {
+		display: block;
+		text-align: right;
+	}
+
+	#sys-modal-shop-basket .modal-footer>:not(:last-child) {
+		margin-right: 0px;
+	}
+
+	#sys-modal-shop-basket .modal-footer>:not(:first-child) {
+		margin-left: 0px;
+		margin-top: 1rem;
+	}
+}
+
+@media (min-width: 768px) {
+	#sys-modal-shop-basket .modal-dialog {
+		max-width: 660px
+	}
+
+	#sys-modal-shop-basket .data .table .thc-3 {
+		display: none;
+	}
+}
+
+@media (min-width: 992px) {
+	#sys-modal-shop-basket .modal-dialog {
+		max-width: 900px
+	}
+
+	#sys-modal-shop-basket .data .table .thc-3 {
+		display: table-cell;
+	}
+}
+
+@media (min-width: 1200px) {
+	#sys-modal-shop-basket .modal-dialog {
+		max-width: 940px
+	}
+
+	#sys-modal-shop-basket .data .table .thc-3 {
+		display: table-cell;
+	}
+}

+ 4 - 4
modules/module_shop.go

@@ -449,25 +449,25 @@ func (this *Modules) RegisterModule_Shop() *Module {
 				wrap.W.WriteHeader(http.StatusOK)
 				wrap.W.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
 				wrap.W.Header().Set("Content-Type", "application/json; charset=utf-8")
-				wrap.W.Write([]byte(wrap.ShopBasket.Info(wrap.CurrHost, wrap.GetSessionId(), wrap.DB, 1)))
+				wrap.W.Write([]byte(wrap.ShopBasket.Info(wrap.R, wrap.CurrHost, wrap.GetSessionId(), wrap.DB, 1)))
 				return
 			} else if wrap.UrlArgs[2] == "plus" && len(wrap.UrlArgs) == 4 && utils.IsNumeric(wrap.UrlArgs[3]) {
 				wrap.W.WriteHeader(http.StatusOK)
 				wrap.W.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
 				wrap.W.Header().Set("Content-Type", "application/json; charset=utf-8")
-				wrap.W.Write([]byte(wrap.ShopBasket.Plus(wrap.CurrHost, wrap.GetSessionId(), wrap.DB, utils.StrToInt(wrap.UrlArgs[3]))))
+				wrap.W.Write([]byte(wrap.ShopBasket.Plus(wrap.R, wrap.CurrHost, wrap.GetSessionId(), wrap.DB, utils.StrToInt(wrap.UrlArgs[3]))))
 				return
 			} else if wrap.UrlArgs[2] == "minus" && len(wrap.UrlArgs) == 4 && utils.IsNumeric(wrap.UrlArgs[3]) {
 				wrap.W.WriteHeader(http.StatusOK)
 				wrap.W.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
 				wrap.W.Header().Set("Content-Type", "application/json; charset=utf-8")
-				wrap.W.Write([]byte(wrap.ShopBasket.Minus(wrap.CurrHost, wrap.GetSessionId(), wrap.DB, utils.StrToInt(wrap.UrlArgs[3]))))
+				wrap.W.Write([]byte(wrap.ShopBasket.Minus(wrap.R, wrap.CurrHost, wrap.GetSessionId(), wrap.DB, utils.StrToInt(wrap.UrlArgs[3]))))
 				return
 			} else if wrap.UrlArgs[2] == "remove" && len(wrap.UrlArgs) == 4 && utils.IsNumeric(wrap.UrlArgs[3]) {
 				wrap.W.WriteHeader(http.StatusOK)
 				wrap.W.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
 				wrap.W.Header().Set("Content-Type", "application/json; charset=utf-8")
-				wrap.W.Write([]byte(wrap.ShopBasket.Remove(wrap.CurrHost, wrap.GetSessionId(), wrap.DB, utils.StrToInt(wrap.UrlArgs[3]))))
+				wrap.W.Write([]byte(wrap.ShopBasket.Remove(wrap.R, wrap.CurrHost, wrap.GetSessionId(), wrap.DB, utils.StrToInt(wrap.UrlArgs[3]))))
 				return
 			}
 		} else if len(wrap.UrlArgs) == 2 && wrap.UrlArgs[0] == "shop" && wrap.UrlArgs[1] != "" {