feat: Wishlist from card actions

- Add remove items from wishlist
- Wishlist icon at nav bar
- Animate wishlist icon in card and navbar
- Remember wished state after refresh as well
diff --git a/erpnext/e_commerce/doctype/wishlist/wishlist.py b/erpnext/e_commerce/doctype/wishlist/wishlist.py
index 94e2754..7527c6f 100644
--- a/erpnext/e_commerce/doctype/wishlist/wishlist.py
+++ b/erpnext/e_commerce/doctype/wishlist/wishlist.py
@@ -3,8 +3,52 @@
 # For license information, please see license.txt
 
 from __future__ import unicode_literals
-# import frappe
+import frappe
 from frappe.model.document import Document
 
 class Wishlist(Document):
 	pass
+
+@frappe.whitelist()
+def add_to_wishlist(item_code, price):
+	"""Insert Item into wishlist."""
+	web_item_data = frappe.db.get_value("Website Item", {"item_code": item_code},
+		["image", "website_warehouse", "name", "item_name"], as_dict=1)
+
+	wished_item_dict = {
+		"item_code": item_code,
+		"item_name": web_item_data.get("item_name"),
+		"website_item": web_item_data.get("name"),
+		"price": frappe.utils.flt(price),
+		"image": web_item_data.get("image"),
+		"website_warehouse": web_item_data.get("website_warehouse")
+	}
+
+	if not frappe.db.exists("Wishlist", frappe.session.user):
+		# initialise wishlist
+		wishlist = frappe.get_doc({"doctype": "Wishlist"})
+		wishlist.user = frappe.session.user
+		wishlist.append("items", wished_item_dict)
+		wishlist.save(ignore_permissions=True)
+	else:
+		wishlist = frappe.get_doc("Wishlist", frappe.session.user)
+		item = wishlist.append('items', wished_item_dict)
+		item.db_insert()
+
+	if hasattr(frappe.local, "cookie_manager"):
+		frappe.local.cookie_manager.set_cookie("wish_count", str(len(wishlist.items)))
+
+@frappe.whitelist()
+def remove_from_wishlist(item_code):
+	if frappe.db.exists("Wishlist Items", {"item_code": item_code}):
+		frappe.db.sql("""
+			delete
+			from `tabWishlist Items`
+			where item_code=%(item_code)s
+		"""%{"item_code": frappe.db.escape(item_code)})
+
+		frappe.db.commit()
+
+		wishlist = frappe.get_doc("Wishlist", frappe.session.user)
+		if hasattr(frappe.local, "cookie_manager"):
+			frappe.local.cookie_manager.set_cookie("wish_count", str(len(wishlist.items)))
\ No newline at end of file
diff --git a/erpnext/e_commerce/doctype/wishlist_items/wishlist_items.json b/erpnext/e_commerce/doctype/wishlist_items/wishlist_items.json
index 29f4066..18065a8 100644
--- a/erpnext/e_commerce/doctype/wishlist_items/wishlist_items.json
+++ b/erpnext/e_commerce/doctype/wishlist_items/wishlist_items.json
@@ -12,6 +12,8 @@
   "item_details_section",
   "description",
   "column_break_7",
+  "section_break_8",
+  "price",
   "image",
   "image_view",
   "warehouse_section",
@@ -52,6 +54,7 @@
   },
   {
    "fetch_from": "item_code.description",
+   "fetch_if_empty": 1,
    "fieldname": "description",
    "fieldtype": "Text Editor",
    "label": "Description"
@@ -62,6 +65,7 @@
   },
   {
    "fetch_from": "item_code.image",
+   "fetch_if_empty": 1,
    "fieldname": "image",
    "fieldtype": "Attach",
    "hidden": 1,
@@ -69,6 +73,7 @@
   },
   {
    "fetch_from": "item_code.image",
+   "fetch_if_empty": 1,
    "fieldname": "image_view",
    "fieldtype": "Image",
    "hidden": 1,
@@ -87,12 +92,21 @@
    "in_list_view": 1,
    "label": "Warehouse",
    "options": "Warehouse"
+  },
+  {
+   "fieldname": "section_break_8",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "price",
+   "fieldtype": "Float",
+   "label": "Price"
   }
  ],
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-03-10 19:13:41.310816",
+ "modified": "2021-03-12 18:23:03.487891",
  "modified_by": "Administrator",
  "module": "E-commerce",
  "name": "Wishlist Items",
diff --git a/erpnext/e_commerce/product_query.py b/erpnext/e_commerce/product_query.py
index 9743b76..28d33e6 100644
--- a/erpnext/e_commerce/product_query.py
+++ b/erpnext/e_commerce/product_query.py
@@ -67,6 +67,7 @@
 			product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get('product_info')
 			if product_info:
 				item.formatted_price = (product_info.get('price') or {}).get('formatted_price')
+				item.price = product_info['price'].get('price_list_rate')
 
 			if self.settings.show_stock_availability and item.get("website_warehouse"):
 				stock_qty = frappe.utils.flt(
@@ -78,6 +79,11 @@
 							"actual_qty")
 						)
 				item.in_stock = "green" if stock_qty else "red"
+
+			item.wished = False
+			if frappe.db.exists("Wishlist Items", {"item_code": item.item_code}):
+				item.wished = True
+
 		return result
 
 	def query_items(self, conditions, or_conditions, substitutions, start=0):
diff --git a/erpnext/e_commerce/shopping_cart/cart.py b/erpnext/e_commerce/shopping_cart/cart.py
index c2c94a3..011f29c 100644
--- a/erpnext/e_commerce/shopping_cart/cart.py
+++ b/erpnext/e_commerce/shopping_cart/cart.py
@@ -138,7 +138,7 @@
 				"additional_notes": additional_notes
 			})
 		else:
-			quotation_items[0].qty = qty + 1
+			quotation_items[0].qty = qty
 			quotation_items[0].additional_notes = additional_notes
 
 	apply_cart_settings(quotation=quotation)
diff --git a/erpnext/public/build.json b/erpnext/public/build.json
index 6b70dab..a891121 100644
--- a/erpnext/public/build.json
+++ b/erpnext/public/build.json
@@ -11,7 +11,8 @@
 	],
 	"js/erpnext-web.min.js": [
 		"public/js/website_utils.js",
-		"public/js/shopping_cart.js"
+		"public/js/shopping_cart.js",
+		"public/js/wishlist.js"
 	],
 	"css/erpnext-web.css": [
 		"public/scss/website.scss",
diff --git a/erpnext/public/js/wishlist.js b/erpnext/public/js/wishlist.js
new file mode 100644
index 0000000..328bdb9
--- /dev/null
+++ b/erpnext/public/js/wishlist.js
@@ -0,0 +1,39 @@
+frappe.provide("erpnext.e_commerce");
+var wishlist = erpnext.e_commerce;
+
+frappe.ready(function() {
+	$(".wishlist").toggleClass('hidden', true);
+	wishlist.set_wishlist_count();
+});
+
+$.extend(wishlist, {
+	set_wishlist_count: function() {
+		var wish_count = frappe.get_cookie("wish_count");
+		if(frappe.session.user==="Guest") {
+			wish_count = 0;
+		}
+
+		if(wish_count) {
+			$(".wishlist").toggleClass('hidden', false);
+		}
+
+		var $wishlist = $('.wishlist-icon');
+		var $badge = $wishlist.find("#wish-count");
+
+		if(parseInt(wish_count) === 0 || wish_count === undefined) {
+			$wishlist.css("display", "none");
+		}
+		else {
+			$wishlist.css("display", "inline");
+		}
+		if(wish_count) {
+			$badge.html(wish_count);
+			$wishlist.addClass('cart-animate');
+			setTimeout(() => {
+				$wishlist.removeClass('cart-animate');
+			}, 500);
+		} else {
+			$badge.remove();
+		}
+	}
+});
\ No newline at end of file
diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss
index 8380f6c..3d66f14 100644
--- a/erpnext/public/scss/shopping_cart.scss
+++ b/erpnext/public/scss/shopping_cart.scss
@@ -349,20 +349,20 @@
 	}
 }
 
-.cart-icon {
-	.cart-badge {
-		position: relative;
-		top: -10px;
-		left: -12px;
-		background: var(--red-600);
-		width: 16px;
-		align-items: center;
-		height: 16px;
-		font-size: 10px;
-		border-radius: 50%;
-	}
+
+.shopping-badge {
+	position: relative;
+	top: -10px;
+	left: -12px;
+	background: var(--red-600);
+	width: 16px;
+	align-items: center;
+	height: 16px;
+	font-size: 10px;
+	border-radius: 50%;
 }
 
+
 .cart-animate {
 	animation: wiggle 0.5s linear;
 }
@@ -555,7 +555,28 @@
 	margin-left: 12px;
 }
 
-.wish-icon {
+.like-animate {
+	animation: expand cubic-bezier(0.04, 0.4, 0.5, 0.95) 1.6s forwards 1;
+}
+
+@keyframes expand {
+	30% {
+	  transform: scale(1.6);
+	}
+	50% {
+	  transform: scale(0.8);
+	}
+	70% {
+		transform: scale(1.3);
+	}
+	100% {
+	  transform: scale(1);
+	}
+  }
+
+@keyframes heart { 0%, 17.5% { font-size: 0; } }
+
+.not-wished {
 	cursor: pointer;
 	stroke: #F47A7A !important;
 
@@ -565,10 +586,8 @@
 }
 
 .wished {
-		.wish-icon {
-			stroke: none;
-			fill: #F47A7A !important;
-		}
+	stroke: none;
+	fill: #F47A7A !important;
 }
 
 .list-row-checkbox {
diff --git a/erpnext/templates/includes/macros.html b/erpnext/templates/includes/macros.html
index 818316c..743daaf 100644
--- a/erpnext/templates/includes/macros.html
+++ b/erpnext/templates/includes/macros.html
@@ -127,12 +127,11 @@
 			<span class="indicator {{ item.in_stock }} card-indicator"></span>
 		{% endif %}
 		{% if not item.has_variants %}
-			<input class="level-item list-row-checkbox hidden-xs"
-				type="checkbox" data-name="{{ title }}" style="display: none !important;">
 			<div class="like-action"
-				data-name="{{ title }}" data-doctype="Item">
+				data-item-code="{{ item.item_code }}" data-price="{{ item.price }}">
 				<svg class="icon sm">
-					<use class="wish-icon" href="#icon-heart"></use>
+					{%- set icon_class = "wished" if item.wished else "not-wished"-%}
+					<use class="{{ icon_class }} wish-icon" href="#icon-heart"></use>
 				</svg>
 			</div>
 		{% endif %}
diff --git a/erpnext/templates/includes/navbar/navbar_items.html b/erpnext/templates/includes/navbar/navbar_items.html
index 2912206..54ed98a 100644
--- a/erpnext/templates/includes/navbar/navbar_items.html
+++ b/erpnext/templates/includes/navbar/navbar_items.html
@@ -6,7 +6,15 @@
 			<svg class="icon icon-lg">
 				<use href="#icon-assets"></use>
 			</svg>
-			<span class="badge badge-primary cart-badge" id="cart-count"></span>
+			<span class="badge badge-primary shopping-badge" id="cart-count"></span>
+		</a>
+	</li>
+	<li class="wishlist wishlist-icon hidden">
+		<a class="nav-link" href="/cart">
+			<svg class="icon icon-lg">
+				<use href="#icon-heart"></use>
+			</svg>
+			<span class="badge badge-primary shopping-badge" id="wish-count"></span>
 		</a>
 	 </li>
 {% endblock %}
diff --git a/erpnext/www/all-products/index.js b/erpnext/www/all-products/index.js
index 4572ee7..fc1a3f4 100644
--- a/erpnext/www/all-products/index.js
+++ b/erpnext/www/all-products/index.js
@@ -73,6 +73,11 @@
 		}
 
 		bind_card_actions() {
+			this.bind_add_to_cart_action();
+			this.bind_wishlist_action();
+		}
+
+		bind_add_to_cart_action() {
 			$('.page_content').on('click', '.btn-add-to-cart-list', (e) => {
 				const $btn = $(e.currentTarget);
 				$btn.prop('disabled', true);
@@ -91,18 +96,77 @@
 		animate_add_to_cart(button) {
 			// Create 'added to cart' animation
 			let btn_id = "#" + button[0].id;
-			button.removeClass('not-added');
-			button.addClass('added-to-cart');
+			this.toggle_button_class(button, 'not-added', 'added-to-cart');
 			$(btn_id).text('Added to Cart');
 
 			// undo
 			setTimeout(() => {
-				button.removeClass('added-to-cart');
-				button.addClass('not-added');
+				this.toggle_button_class(button, 'added-to-cart', 'not-added');
 				$(btn_id).text('Add to Cart');
 			}, 2000);
 		}
 
+		bind_wishlist_action() {
+			$('.page_content').on('click', '.like-action', (e) => {
+				const $btn = $(e.currentTarget);
+				const $wish_icon = $btn.find('.wish-icon');
+				let me = this;
+
+				if ($wish_icon.hasClass('wished')) {
+					// un-wish item
+					$btn.removeClass("like-animate");
+					this.toggle_button_class($wish_icon, 'wished', 'not-wished');
+					frappe.call({
+						type: "POST",
+						method: "erpnext.e_commerce.doctype.wishlist.wishlist.remove_from_wishlist",
+						args: {
+							item_code: $btn.data('item-code')
+						},
+						callback: function (r) {
+							if (r.exc) {
+								me.toggle_button_class($wish_icon, 'wished', 'not-wished');
+								frappe.msgprint({
+									message: __("Sorry, something went wrong. Please refresh."),
+									indicator: "red",
+									title: __("Note")}
+								);
+							} else {
+								erpnext.e_commerce.set_wishlist_count();
+							}
+						}
+					});
+				} else {
+					$btn.addClass("like-animate");
+					this.toggle_button_class($wish_icon, 'not-wished', 'wished');
+					frappe.call({
+						type: "POST",
+						method: "erpnext.e_commerce.doctype.wishlist.wishlist.add_to_wishlist",
+						args: {
+							item_code: $btn.data('item-code'),
+							price: $btn.data('price')
+						},
+						callback: function (r) {
+							if (r.exc) {
+								me.toggle_button_class($wish_icon, 'wished', 'not-wished');
+								frappe.msgprint({
+									message: __("Sorry, something went wrong. Please refresh."),
+									indicator: "red",
+									title: __("Note")}
+								);
+							} else {
+								erpnext.e_commerce.set_wishlist_count();
+							}
+						}
+					});
+				}
+			});
+		}
+
+		toggle_button_class(button, remove, add) {
+			button.removeClass(remove);
+			button.addClass(add);
+		}
+
 		bind_search() {
 			$('input[type=search]').on('keydown', (e) => {
 				if (e.keyCode === 13) {