Website: Product Configurator and Bootstrap 4 (#15965)

- Refactored Homepage with customisable Hero Section
- New Homepage Section to add content on Homepage as cards or using Custom HTML
- Products page at "/all-products" with customisable filters
- Item Configure dialog to find an Item Variant filtered by attribute values
- Contact Us dialog on Item page
- Customisable Item page content using the Website Content field
diff --git a/erpnext/templates/includes/address_row.html b/erpnext/templates/includes/address_row.html
index bfc035a..dadd2df 100644
--- a/erpnext/templates/includes/address_row.html
+++ b/erpnext/templates/includes/address_row.html
@@ -1,12 +1,12 @@
-<div class="web-list-item">
-    <a href="/addresses?name={{ doc.name | urlencode }}" class="no-decoration">
+<div class="web-list-item mb-3">
+    <a href="/addresses?name={{ doc.name | urlencode }}" class="no-underline text-reset">
 	    <div class="row">
-	        <div class="col-xs-3">
+	        <div class="col-3">
 	                 <span class="indicator {{ "red" if doc.address_type=="Office" else "green" if doc.address_type=="Billing" else "blue" if doc.address_type=="Shipping" else "darkgrey" }}">{{ doc.address_title }}</span>
 			</div>
-			<div class="col-xs-2"> {{ _(doc.address_type) }} </div>
-			<div class="col-xs-2"> {{ doc.city }} </div>
-			<div class="col-xs-5 text-right small text-muted">
+			<div class="col-2"> {{ _(doc.address_type) }} </div>
+			<div class="col-2"> {{ doc.city }} </div>
+			<div class="col-5 text-right small text-muted">
 	            {{ frappe.get_doc(doc).get_display() }}
 	        </div>
 	    </div>
diff --git a/erpnext/templates/includes/cart.js b/erpnext/templates/includes/cart.js
index 51be954..983898b 100644
--- a/erpnext/templates/includes/cart.js
+++ b/erpnext/templates/includes/cart.js
@@ -16,41 +16,33 @@
 	bind_events: function() {
 		shopping_cart.bind_address_select();
 		shopping_cart.bind_place_order();
+		shopping_cart.bind_request_quotation();
 		shopping_cart.bind_change_qty();
+		shopping_cart.bind_change_notes();
 		shopping_cart.bind_dropdown_cart_buttons();
 	},
 
 	bind_address_select: function() {
-		$(".cart-addresses").find('input[data-address-name]').on("click", function() {
-			if($(this).prop("checked")) {
-				var me = this;
+		$(".cart-addresses").on('click', '.address-card', function(e) {
+			const $card = $(e.currentTarget);
+			const address_fieldname = $card.closest('[data-fieldname]').attr('data-fieldname');
+			const address_name = $card.closest('[data-address-name]').attr('data-address-name');
 
-				// uncheck other shipping or billing addresses:
-				if ( $(this).is('input[data-fieldname=customer_address]') ) {
-					$('input[data-fieldname=customer_address]').not(this).prop('checked', false);
-				} else {
-					$('input[data-fieldname=shipping_address_name]').not(this).prop('checked', false);
-				}
-
-				return frappe.call({
-					type: "POST",
-					method: "erpnext.shopping_cart.cart.update_cart_address",
-					freeze: true,
-					args: {
-						address_fieldname: $(this).attr("data-fieldname"),
-						address_name: $(this).attr("data-address-name")
-					},
-					callback: function(r) {
-						if(!r.exc) {
-							$(".cart-tax-items").html(r.message.taxes);
-						}
+			return frappe.call({
+				type: "POST",
+				method: "erpnext.shopping_cart.cart.update_cart_address",
+				freeze: true,
+				args: {
+					address_fieldname,
+					address_name
+				},
+				callback: function(r) {
+					if(!r.exc) {
+						$(".cart-tax-items").html(r.message.taxes);
 					}
-				});
-			} else {
-				return false;
-			}
+				}
+			});
 		});
-
 	},
 
 	bind_place_order: function() {
@@ -59,12 +51,18 @@
 		});
 	},
 
+	bind_request_quotation: function() {
+		$('.btn-request-for-quotation').on('click', function() {
+			shopping_cart.request_quotation(this);
+		});
+	},
+
 	bind_change_qty: function() {
 		// bind update button
 		$(".cart-items").on("change", ".cart-qty", function() {
 			var item_code = $(this).attr("data-item-code");
 			var newVal = $(this).val();
-			shopping_cart.shopping_cart_update(item_code, newVal);
+			shopping_cart.shopping_cart_update({item_code, qty: newVal});
 		});
 
 		$(".cart-items").on('click', '.number-spinner button', function () {
@@ -82,7 +80,21 @@
 			}
 			input.val(newVal);
 			var item_code = input.attr("data-item-code");
-			shopping_cart.shopping_cart_update(item_code, newVal);
+			shopping_cart.shopping_cart_update({item_code, qty: newVal});
+		});
+	},
+
+	bind_change_notes: function() {
+		$('.cart-items').on('change', 'textarea', function() {
+			const $textarea = $(this);
+			const item_code = $textarea.attr('data-item-code');
+			const qty = $textarea.closest('tr').find('.cart-qty').val();
+			const notes = $textarea.val();
+			shopping_cart.shopping_cart_update({
+				item_code,
+				qty,
+				additional_notes: notes
+			});
 		});
 	},
 
@@ -150,7 +162,32 @@
 						.html(msg || frappe._("Something went wrong!"))
 						.toggle(true);
 				} else {
-					window.location.href = "/orders/" + encodeURIComponent(r.message);
+					window.open('/orders/' + encodeURIComponent(r.message), '_blank');
+					window.location.reload();
+				}
+			}
+		});
+	},
+
+	request_quotation: function(btn) {
+		return frappe.call({
+			type: "POST",
+			method: "erpnext.shopping_cart.cart.request_for_quotation",
+			btn: btn,
+			callback: function(r) {
+				if(r.exc) {
+					var msg = "";
+					if(r._server_messages) {
+						msg = JSON.parse(r._server_messages || []).join("<br>");
+					}
+
+					$("#cart-error")
+						.empty()
+						.html(msg || frappe._("Something went wrong!"))
+						.toggle(true);
+				} else {
+					window.open('/printview?doctype=Quotation&name=' + r.message, '_blank');
+					window.location.reload();
 				}
 			}
 		});
diff --git a/erpnext/templates/includes/cart/address_card.html b/erpnext/templates/includes/cart/address_card.html
new file mode 100644
index 0000000..c91723e
--- /dev/null
+++ b/erpnext/templates/includes/cart/address_card.html
@@ -0,0 +1,12 @@
+<div class="card address-card h-100">
+	<div class="check" style="position: absolute; right: 15px; top: 15px;">
+		<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check"><polyline points="20 6 9 17 4 12"></polyline></svg>
+	</div>
+	<div class="card-body">
+		<h5 class="card-title">{{ address.name }}</h5>
+		<p class="card-text text-muted">
+			{{ address.display }}
+		</p>
+		<a href="/addresses?name={{address.name}}" class="card-link">{{ _('Edit') }}</a>
+	</div>
+</div>
diff --git a/erpnext/templates/includes/cart/cart_address.html b/erpnext/templates/includes/cart/cart_address.html
index 7bd9256..2c90f8c 100644
--- a/erpnext/templates/includes/cart/cart_address.html
+++ b/erpnext/templates/includes/cart/cart_address.html
@@ -1,26 +1,141 @@
 {% from "erpnext/templates/includes/cart/cart_macros.html" import show_address %}
-<div class="row">
-	{% if addresses|length == 1%}
-		{% set select_address = True %}
-	{% endif %}
-	<div class="col-sm-6">
-		<div class="h6 text-uppercase">{{ _("Shipping Address") }}</div>
-		<div id="cart-shipping-address" class="panel-group"
-			data-fieldname="shipping_address_name">
-            {% for address in shipping_addresses %}
-                {{ show_address(address, doc, "shipping_address_name", select_address) }}
-            {% endfor %}
-        </div>
-		<a class="btn btn-default btn-sm" href="/addresses">
-			{{ _("Manage Addresses") }}</a>
-	</div>
-	<div class="col-sm-6">
-        <div class="h6 text-uppercase">{{ _("Billing Address") }}</div>
-		<div id="cart-billing-address" class="panel-group"
-			data-fieldname="customer_address">
-            {% for address in billing_addresses %}
-                {{ show_address(address, doc, "customer_address", select_address) }}
-            {% endfor %}
-        </div>
+
+{% if addresses | length == 1%}
+	{% set select_address = True %}
+{% endif %}
+
+<div class="mb-3" data-section="shipping-address">
+	<h6 class="text-uppercase">{{ _("Shipping Address") }}</h6>
+	<div class="row no-gutters" data-fieldname="shipping_address_name">
+		{% for address in shipping_addresses %}
+			<div class="mr-3 mb-3 w-25" data-address-name="{{address.name}}" {% if doc.shipping_address_name == address.name %} data-active {% endif %}>
+				{% include "templates/includes/cart/address_card.html" %}
+			</div>
+		{% endfor %}
 	</div>
 </div>
+<div class="mb-3" data-section="billing-address">
+	<h6 class="text-uppercase">{{ _("Billing Address") }}</h6>
+	<div class="row no-gutters" data-fieldname="customer_address">
+		{% for address in billing_addresses %}
+			<div class="mr-3 mb-3 w-25" data-address-name="{{address.name}}" {% if doc.customer_address == address.name %} data-active {% endif %}>
+				{% include "templates/includes/cart/address_card.html" %}
+			</div>
+		{% endfor %}
+	</div>
+</div>
+<div class="custom-control custom-checkbox">
+	<input type="checkbox" class="custom-control-input" id="input_same_billing" checked>
+	<label class="custom-control-label" for="input_same_billing">{{ _('Billing Address is same as Shipping Address') }}</label>
+</div>
+<button class="btn btn-outline-primary btn-sm mt-3 btn-new-address">{{ _("Add a new address") }}</button>
+
+<script>
+frappe.ready(() => {
+	$(document).on('click', '.address-card', (e) => {
+		const $target = $(e.currentTarget);
+		const $section = $target.closest('[data-section]');
+		$section.find('.address-card').removeClass('active');
+		$target.addClass('active');
+	});
+
+	$('#input_same_billing').change((e) => {
+		const $check = $(e.target);
+		toggle_billing_address_section(!$check.is(':checked'));
+	});
+
+	$('.btn-new-address').click(() => {
+		const d = new frappe.ui.Dialog({
+			title: __('New Address'),
+			fields: [
+				{
+					label: __('Address Title'),
+					fieldname: 'address_title',
+					fieldtype: 'Data',
+					reqd: 1
+				},
+				{
+					label: __('Address Type'),
+					fieldname: 'address_type',
+					fieldtype: 'Select',
+					options: [
+						'Billing',
+						'Shipping'
+					],
+					reqd: 1
+				},
+				{
+					label: __('Address Line 1'),
+					fieldname: 'address_line1',
+					fieldtype: 'Data',
+					reqd: 1
+				},
+				{
+					label: __('Address Line 2'),
+					fieldname: 'address_line2',
+					fieldtype: 'Data'
+				},
+				{
+					label: __('City/Town'),
+					fieldname: 'city',
+					fieldtype: 'Data',
+					reqd: 1
+				},
+				{
+					label: __('State'),
+					fieldname: 'state',
+					fieldtype: 'Data'
+				},
+				{
+					label: __('Pin Code'),
+					fieldname: 'pincode',
+					fieldtype: 'Data'
+				},
+				{
+					label: __('Country'),
+					fieldname: 'country',
+					fieldtype: 'Data',
+					reqd: 1
+				},
+			],
+			primary_action_label: __('Save'),
+			primary_action: (values) => {
+				frappe.call('erpnext.shopping_cart.cart.add_new_address', { doc: values })
+					.then(r => {
+						d.hide();
+						window.location.reload();
+					});
+			}
+		})
+
+		d.show();
+	});
+
+	function setup_state() {
+		const shipping_address = $('[data-section="shipping-address"]')
+			.find('[data-address-name][data-active]').attr('data-address-name');
+
+		const billing_address = $('[data-section="billing-address"]')
+			.find('[data-address-name][data-active]').attr('data-address-name');
+
+		$('#input_same_billing').prop('checked', shipping_address === billing_address).trigger('change');
+
+		if (!shipping_address && !billing_address) {
+			$('#input_same_billing').prop('checked', true).trigger('change');
+		}
+
+		if (shipping_address) {
+			$(`[data-section="shipping-address"] [data-address-name="${shipping_address}"] .address-card`).addClass('active');
+		}
+		if (billing_address) {
+			$(`[data-section="billing-address"] [data-address-name="${billing_address}"] .address-card`).addClass('active');
+		}
+	}
+
+	setup_state();
+
+	function toggle_billing_address_section(flag) {
+		$('[data-section="billing-address"]').toggle(flag);
+	}
+});
+</script>
diff --git a/erpnext/templates/includes/cart/cart_items.html b/erpnext/templates/includes/cart/cart_items.html
index 65b81d9..ca5744b 100644
--- a/erpnext/templates/includes/cart/cart_items.html
+++ b/erpnext/templates/includes/cart/cart_items.html
@@ -1,31 +1,42 @@
-{% from "erpnext/templates/includes/order/order_macros.html" import item_name_and_description %}
-{% from "erpnext/templates/includes/order/order_macros.html" import item_name_and_description_cart %}
-
 {% for d in doc.items %}
-<div class="row checkout">
-    <div class="col-sm-8 col-xs-6 col-name-description">
-        {{ item_name_and_description(d) }}
-    </div>
-    <div class="col-sm-2 col-xs-3 text-right col-qty">
-        <span style="display: inline-block">
-			<div class="input-group number-spinner">
-                <span class="input-group-btn">
-                    <button class="btn btn-default cart-btn" data-dir="dwn">
-                        –</button>
-                </span>
-            <input class="form-control text-right cart-qty"
-            value = "{{ d.get_formatted('qty') }}"
-            data-item-code="{{ d.item_code }}">
-                <span class="input-group-btn">
-                    <button class="btn btn-default cart-btn" data-dir="up" style="margin-left:-2px;">
-                        +</button>
-                </span>
-			</div>
+<tr data-name="{{ d.name }}">
+	<td>
+		<div class="font-weight-bold">
+			{{ d.item_name }}
+		</div>
+		<div>
+			{{ d.item_code }}
+		</div>
+		{%- set variant_of = frappe.db.get_value('Item', d.item_code, 'variant_of') %}
+		{% if variant_of %}
+		<span class="text-muted">
+			{{ _('Variant of') }} <a href="{{frappe.db.get_value('Item', variant_of, 'route')}}">{{ variant_of }}</a>
 		</span>
-	</div>
-    <div class="col-sm-2 col-xs-3 text-right col-amount">
-        {{ d.get_formatted("amount") }}
-        <p class="text-muted small item-rate">{{ _("Rate") }}&nbsp;{{ d.get_formatted("rate") }}</p>
-    </div>
-</div>
-{% endfor %}
\ No newline at end of file
+		{% endif %}
+		<div class="mt-2">
+			<textarea data-item-code="{{d.item_code}}" class="form-control" rows="2" placeholder="{{ _('Add notes') }}">{{d.additional_notes or ''}}</textarea>
+		</div>
+	</td>
+	<td class="text-right">
+		<div class="input-group number-spinner">
+			<span class="input-group-prepend d-none d-sm-inline-block">
+				<button class="btn btn-outline-secondary cart-btn" data-dir="dwn">–</button>
+			</span>
+			<input class="form-control text-right cart-qty border-secondary" value="{{ d.get_formatted('qty') }}" data-item-code="{{ d.item_code }}">
+			<span class="input-group-append d-none d-sm-inline-block">
+				<button class="btn btn-outline-secondary cart-btn" data-dir="up">+</button>
+			</span>
+		</div>
+	</td>
+	{% if cart_settings.enable_checkout %}
+	<td class="text-right">
+		<div>
+			{{ d.get_formatted('amount') }}
+		</div>
+		<span class="text-muted">
+			{{ _('Rate:') }} {{ d.get_formatted('rate') }}
+		</span>
+	</td>
+	{% endif %}
+</tr>
+{% endfor %}
diff --git a/erpnext/templates/includes/footer/footer_extension.html b/erpnext/templates/includes/footer/footer_extension.html
index 23a6a34..8cf3081 100644
--- a/erpnext/templates/includes/footer/footer_extension.html
+++ b/erpnext/templates/includes/footer/footer_extension.html
@@ -1,11 +1,14 @@
 {% if not hide_footer_signup %}
-<div class='input-group input-group-sm pull-right footer-subscribe'>
-	<input class="form-control" type="text" id="footer-subscribe-email"
-		placeholder="{{ _('Your email address') }}...">
-	<span class='input-group-btn'>
-		<button class="btn btn-default" type="button"
-			id="footer-subscribe-button">{{ _("Get Updates") }}</button>
-	</span>
+<div class="input-group">
+	<input type="text" class="form-control border-secondary"
+		id="footer-subscribe-email"
+		placeholder="{{ _('Your email address...') }}"
+		aria-label="{{ _('Your email address...') }}"
+		aria-describedby="footer-subscribe-button">
+	<div class="input-group-append">
+		<button class="btn btn-outline-secondary"
+			type="button" id="footer-subscribe-button">{{ _("Get Updates") }}</button>
+	</div>
 </div>
 
 <script>
diff --git a/erpnext/templates/includes/footer/footer_powered.html b/erpnext/templates/includes/footer/footer_powered.html
index e9d5f56..faf5e92 100644
--- a/erpnext/templates/includes/footer/footer_powered.html
+++ b/erpnext/templates/includes/footer/footer_powered.html
@@ -1,2 +1 @@
-<a href="https://erpnext.com?source=website_footer" target="_blank" class="text-muted">
-		Powered by ERPNext</a>
+<a href="https://erpnext.com?source=website_footer" target="_blank" class="text-muted">Powered by ERPNext</a>
diff --git a/erpnext/templates/includes/macros.html b/erpnext/templates/includes/macros.html
index 863d48e..2d27915 100644
--- a/erpnext/templates/includes/macros.html
+++ b/erpnext/templates/includes/macros.html
@@ -1,7 +1,4 @@
 {% macro product_image_square(website_image, css_class="") %}
-{% if website_image -%}
-	<meta itemprop="image" content="{{ frappe.utils.quoted(website_image) | abs_url }}"></meta>
-{%- endif %}
 <div class="product-image product-image-square
 	{% if not website_image -%} missing-image {%- endif %} {{ css_class }}"
 	{% if website_image -%}
@@ -11,12 +8,8 @@
 {% endmacro %}
 
 {% macro product_image(website_image, css_class="") %}
-    <div class="product-image {% if not website_image -%} missing-image {%- endif %} {{ css_class }}">
-    	{% if website_image -%}
-			<a href="{{ frappe.utils.quoted(website_image) }}">
-				<img itemprop="image" src="{{ frappe.utils.quoted(website_image) | abs_url }}" class="img-responsive">
-			</a>
-    	{%- endif %}
+    <div class="border text-center rounded h-100 {{ css_class }}" style="overflow: hidden;">
+		<img itemprop="image" class="website-image h-100 w-100" src="{{ frappe.utils.quoted(website_image or 'no-image.jpg') | abs_url }}">
     </div>
 {% endmacro %}
 
@@ -33,3 +26,35 @@
     	{%- endif %}
     </div>
 {% endmacro %}
+
+{% macro render_homepage_section(section) %}
+
+{% if section.section_based_on == 'Custom HTML' and section.section_html %}
+	{{ section.section_html }}
+{% elif section.section_based_on == 'Cards' %}
+<section class="container my-5">
+	<h3>{{ section.name }}</h3>
+
+	<div class="row">
+		{% for card in section.section_cards %}
+		<div class="col-md-{{ section.column_value }} mb-4">
+			<div class="card h-100 justify-content-between">
+				{% if card.image %}
+				<div class="website-image-lazy" data-class="card-img-top h-100" data-src="{{ card.image }}" data-alt="{{ card.title }}"></div>
+				{% endif %}
+				<div class="card-body">
+					<h5 class="card-title">{{ card.title }}</h5>
+					<p class="card-subtitle mb-2 text-muted">{{ card.subtitle or '' }}</p>
+					<p class="card-text">{{ card.content | truncate(140, True) }}</p>
+				</div>
+				<div class="card-body flex-grow-0">
+					<a href="{{ card.route }}" class="card-link">{{ _('More details') }}</a>
+				</div>
+			</div>
+		</div>
+		{% endfor %}
+	</div>
+</section>
+{% endif %}
+
+{% endmacro %}
\ No newline at end of file
diff --git a/erpnext/templates/includes/navbar/navbar_items.html b/erpnext/templates/includes/navbar/navbar_items.html
index faf8adf..4daf0e7 100644
--- a/erpnext/templates/includes/navbar/navbar_items.html
+++ b/erpnext/templates/includes/navbar/navbar_items.html
@@ -1,12 +1,10 @@
 {% extends 'frappe/templates/includes/navbar/navbar_items.html' %}
 
 {% block navbar_right_extension %}
-	<li class="shopping-cart hidden">
-		<div class="cart-icon">
-			<a class="dropdown-toggle" href="#" data-toggle="dropdown" id="navLogin">
-				{{ _("Cart") }} <span class="badge-wrapper" id="cart-count"></span>
-			</a>
-			<div id="cart-overlay" class="dropdown-menu shopping-cart-menu"></div>
-		</div>
+	<li class="shopping-cart cart-icon hidden">
+		<a href="/cart" class="nav-link">
+			{{ _("Cart") }}
+			<span class="badge badge-primary" id="cart-count"></span>
+		</a>
 	 </li>
 {% endblock %}
\ No newline at end of file
diff --git a/erpnext/templates/includes/order/order_macros.html b/erpnext/templates/includes/order/order_macros.html
index c2dff8c..da4fb8c 100644
--- a/erpnext/templates/includes/order/order_macros.html
+++ b/erpnext/templates/includes/order/order_macros.html
@@ -9,7 +9,9 @@
         </div>
         <div class="col-xs-8 col-sm-10">
             {{ d.item_code }}
-            <div class="text-muted small item-description">{{ d.description }}</div>
+            <div class="text-muted small item-description">
+				{{ html2text(d.description) | truncate(140) }}
+			</div>
         </div>
     </div>
 {% endmacro %}
@@ -25,14 +27,14 @@
            {{ d.item_name|truncate(25) }}
 			<div class="input-group number-spinner">
                 <span class="input-group-btn">
-                    <button class="btn btn-default cart-btn" data-dir="dwn">
+                    <button class="btn btn-light cart-btn" data-dir="dwn">
                         –</button>
                 </span>
 	            <input class="form-control text-right cart-qty"
 		            value = "{{ d.get_formatted('qty') }}"
 		            data-item-code="{{ d.item_code }}">
                 <span class="input-group-btn">
-                    <button class="btn btn-default cart-btn" data-dir="up">
+                    <button class="btn btn-light cart-btn" data-dir="up">
                         +</button>
                 </span>
 			</div>
diff --git a/erpnext/templates/includes/order/order_taxes.html b/erpnext/templates/includes/order/order_taxes.html
index 462d77d..1d26700 100644
--- a/erpnext/templates/includes/order/order_taxes.html
+++ b/erpnext/templates/includes/order/order_taxes.html
@@ -1,24 +1,32 @@
 {% if doc.taxes %}
-<div class="row tax-net-total-row">
-    <div class="col-xs-6 text-right">{{ _("Net Total") }}</div>
-    <div class="col-xs-6 text-right">
-        {{ doc.get_formatted("net_total") }}</div>
-</div>
+<tr>
+	<td class="text-right" colspan="2">
+		{{ _("Net Total") }}
+	</td>
+	<td class="text-right">
+		{{ doc.get_formatted("net_total") }}
+	</td>
+</tr>
 {% endif %}
+
 {% for d in doc.taxes %}
 {% if d.base_tax_amount > 0 %}
-<div class="row tax-row">
-    <div class="col-xs-6 text-right">{{ d.description }}</div>
-    <div class="col-xs-6 text-right">
-        {{ d.get_formatted("base_tax_amount") }}</div>
-</div>
+<tr>
+	<td class="text-right" colspan="2">
+		{{ d.description }}
+	</td>
+	<td class="text-right">
+		{{ d.get_formatted("base_tax_amount") }}
+	</td>
+</tr>
 {% endif %}
 {% endfor %}
-<div class="row tax-grand-total-row">
-    <div class="col-xs-6 text-right text-uppercase h6 text-muted">{{ _("Grand Total") }}</div>
-    <div class="col-xs-6 text-right">
-        <span class="tax-grand-total bold">
-            {{ doc.get_formatted("grand_total") }}
-        </span>
-    </div>
-</div>
+
+<tr>
+	<th class="text-right" colspan="2">
+		{{ _("Grand Total") }}
+	</th>
+	<th class="text-right">
+		{{ doc.get_formatted("grand_total") }}
+	</th>
+</tr>
diff --git a/erpnext/templates/includes/product_page.js b/erpnext/templates/includes/product_page.js
deleted file mode 100644
index ef69e20..0000000
--- a/erpnext/templates/includes/product_page.js
+++ /dev/null
@@ -1,215 +0,0 @@
-// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-// License: GNU General Public License v3. See license.txt
-
-frappe.ready(function() {
-	window.item_code = $('[itemscope] [itemprop="productID"]').text().trim();
-	var qty = 0;
-
-	frappe.call({
-		type: "POST",
-		method: "erpnext.shopping_cart.product_info.get_product_info_for_website",
-		args: {
-			item_code: get_item_code()
-		},
-		callback: function(r) {
-			if(r.message) {
-				if(r.message.cart_settings.enabled) {
-					$(".item-cart, .item-price, .item-stock").toggleClass("hide", (!!!r.message.product_info.price || !!!r.message.product_info.in_stock));
-				}
-				if(r.message.cart_settings.show_price) {
-					$(".item-price").toggleClass("hide", false);
-				}
-				if(r.message.cart_settings.show_stock_availability) {
-					$(".item-stock").toggleClass("hide", false);
-				}
-				if(r.message.product_info.price) {
-					$(".item-price")
-						.html(r.message.product_info.price.formatted_price_sales_uom + "<div style='font-size: small'>\
-							(" + r.message.product_info.price.formatted_price + " / " + r.message.product_info.uom + ")</div>");
-
-					if(r.message.product_info.in_stock==0) {
-						$(".item-stock").html("<div style='color: red'> <i class='fa fa-close'></i> {{ _("Not in stock") }}</div>");
-					}
-					else if(r.message.product_info.in_stock==1) {
-						var qty_display = "{{ _("In stock") }}";
-						if (r.message.product_info.show_stock_qty) {
-							qty_display += " ("+r.message.product_info.stock_qty+")";
-						}
-						$(".item-stock").html("<div style='color: green'>\
-							<i class='fa fa-check'></i> "+qty_display+"</div>");
-					}
-
-					if(r.message.product_info.qty) {
-						qty = r.message.product_info.qty;
-						toggle_update_cart(r.message.product_info.qty);
-					} else {
-						toggle_update_cart(0);
-					}
-				}
-			}
-		}
-	})
-
-	$("#item-add-to-cart button").on("click", function() {
-		frappe.provide('erpnext.shopping_cart');
-
-		erpnext.shopping_cart.update_cart({
-			item_code: get_item_code(),
-			qty: $("#item-spinner .cart-qty").val(),
-			callback: function(r) {
-				if(!r.exc) {
-					toggle_update_cart(1);
-					qty = 1;
-				}
-			},
-			btn: this,
-		});
-	});
-
-	$("#item-spinner").on('click', '.number-spinner button', function () {
-		var btn = $(this),
-			input = btn.closest('.number-spinner').find('input'),
-			oldValue = input.val().trim(),
-			newVal = 0;
-
-		if (btn.attr('data-dir') == 'up') {
-			newVal = parseInt(oldValue) + 1;
-		} else if (btn.attr('data-dir') == 'dwn')  {
-			if (parseInt(oldValue) > 1) {
-				newVal = parseInt(oldValue) - 1;
-			}
-			else {
-				newVal = parseInt(oldValue);
-			}
-		}
-		input.val(newVal);
-	});
-
-	$("[itemscope] .item-view-attribute .form-control").on("change", function() {
-		try {
-			var item_code = encodeURIComponent(get_item_code());
-
-		} catch(e) {
-			// unable to find variant
-			// then chose the closest available one
-
-			var attribute = $(this).attr("data-attribute");
-			var attribute_value = $(this).val();
-			var item_code = find_closest_match(attribute, attribute_value);
-
-			if (!item_code) {
-				frappe.msgprint(__("Cannot find a matching Item. Please select some other value for {0}.", [attribute]))
-				throw e;
-			}
-		}
-
-		if (window.location.search == ("?variant=" + item_code) || window.location.search.includes(item_code)) {
-			return;
-		}
-
-		window.location.href = window.location.pathname + "?variant=" + item_code;
-	});
-
-	// change the item image src when alternate images are hovered
-	$(document.body).on('mouseover', '.item-alternative-image', (e) => {
-		const $alternative_image = $(e.currentTarget);
-		const src = $alternative_image.find('img').prop('src');
-		$('.item-image img').prop('src', src);
-	});
-});
-
-var toggle_update_cart = function(qty) {
-	$("#item-add-to-cart").toggle(qty ? false : true);
-	$("#item-update-cart")
-		.toggle(qty ? true : false)
-		.find("input").val(qty);
-	$("#item-spinner").toggle(qty ? false : true);
-}
-
-function get_item_code() {
-	var variant_info = window.variant_info;
-	if(variant_info) {
-		var attributes = get_selected_attributes();
-		var no_of_attributes = Object.keys(attributes).length;
-
-		for(var i in variant_info) {
-			var variant = variant_info[i];
-
-			if (variant.attributes.length < no_of_attributes) {
-				// the case when variant has less attributes than template
-				continue;
-			}
-
-			var match = true;
-			for(var j in variant.attributes) {
-				if(attributes[variant.attributes[j].attribute]
-					!= variant.attributes[j].attribute_value
-				) {
-					match = false;
-					break;
-				}
-			}
-			if(match) {
-				return variant.name;
-			}
-		}
-		throw "Unable to match variant";
-	} else {
-		return window.item_code;
-	}
-}
-
-function find_closest_match(selected_attribute, selected_attribute_value) {
-	// find the closest match keeping the selected attribute in focus and get the item code
-
-	var attributes = get_selected_attributes();
-
-	var previous_match_score = 0;
-	var previous_no_of_attributes = 0;
-	var matched;
-
-	var variant_info = window.variant_info;
-	for(var i in variant_info) {
-		var variant = variant_info[i];
-		var match_score = 0;
-		var has_selected_attribute = false;
-
-		for(var j in variant.attributes) {
-			if(attributes[variant.attributes[j].attribute]===variant.attributes[j].attribute_value) {
-				match_score = match_score + 1;
-
-				if (variant.attributes[j].attribute==selected_attribute && variant.attributes[j].attribute_value==selected_attribute_value) {
-					has_selected_attribute = true;
-				}
-			}
-		}
-
-		if (has_selected_attribute
-			&& ((match_score > previous_match_score) || (match_score==previous_match_score && previous_no_of_attributes < variant.attributes.length))) {
-			previous_match_score = match_score;
-			matched = variant;
-			previous_no_of_attributes = variant.attributes.length;
-
-
-		}
-	}
-
-	if (matched) {
-		for (var j in matched.attributes) {
-			var attr = matched.attributes[j];
-			$('[itemscope]')
-				.find(repl('.item-view-attribute .form-control[data-attribute="%(attribute)s"]', attr))
-				.val(attr.attribute_value);
-		}
-
-		return matched.name;
-	}
-}
-
-function get_selected_attributes() {
-	var attributes = {};
-	$('[itemscope]').find(".item-view-attribute .form-control").each(function() {
-		attributes[$(this).attr('data-attribute')] = $(this).val();
-	});
-	return attributes;
-}