feat: Wishlist Page

- Navbar icon with badge count for wishlist
- Wishlist page with cards
- Cards can be moved to cart or removed in a click
- Separated all wishlist related methods into wishlist.js
- Made a common js method(util) to add/remove wishlist items
- Bug fix: Make sure items are removed from session user's wishlist
diff --git a/erpnext/e_commerce/doctype/wishlist/wishlist.py b/erpnext/e_commerce/doctype/wishlist/wishlist.py
index 7527c6f..83bdff6 100644
--- a/erpnext/e_commerce/doctype/wishlist/wishlist.py
+++ b/erpnext/e_commerce/doctype/wishlist/wishlist.py
@@ -10,18 +10,22 @@
 	pass
 
 @frappe.whitelist()
-def add_to_wishlist(item_code, price):
+def add_to_wishlist(item_code, price, formatted_price=None):
 	"""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)
+		["image", "website_warehouse", "name", "item_name", "item_group", "route"]
+		, as_dict=1)
 
 	wished_item_dict = {
 		"item_code": item_code,
 		"item_name": web_item_data.get("item_name"),
+		"item_group": web_item_data.get("item_group"),
 		"website_item": web_item_data.get("name"),
 		"price": frappe.utils.flt(price),
+		"formatted_price": formatted_price,
 		"image": web_item_data.get("image"),
-		"website_warehouse": web_item_data.get("website_warehouse")
+		"warehouse": web_item_data.get("website_warehouse"),
+		"route": web_item_data.get("route")
 	}
 
 	if not frappe.db.exists("Wishlist", frappe.session.user):
diff --git a/erpnext/e_commerce/doctype/wishlist_items/wishlist_items.json b/erpnext/e_commerce/doctype/wishlist_items/wishlist_items.json
index 18065a8..0b13273 100644
--- a/erpnext/e_commerce/doctype/wishlist_items/wishlist_items.json
+++ b/erpnext/e_commerce/doctype/wishlist_items/wishlist_items.json
@@ -9,13 +9,16 @@
   "website_item",
   "column_break_3",
   "item_name",
+  "item_group",
   "item_details_section",
   "description",
   "column_break_7",
-  "section_break_8",
-  "price",
+  "route",
   "image",
   "image_view",
+  "section_break_8",
+  "price",
+  "formatted_price",
   "warehouse_section",
   "warehouse"
  ],
@@ -101,12 +104,28 @@
    "fieldname": "price",
    "fieldtype": "Float",
    "label": "Price"
+  },
+  {
+   "fieldname": "item_group",
+   "fieldtype": "Link",
+   "label": "Item Group",
+   "options": "Item Group"
+  },
+  {
+   "fieldname": "route",
+   "fieldtype": "Small Text",
+   "label": "Route"
+  },
+  {
+   "fieldname": "formatted_price",
+   "fieldtype": "Data",
+   "label": "Formatted Price"
   }
  ],
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-03-12 18:23:03.487891",
+ "modified": "2021-03-15 16:37:40.405333",
  "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 28d33e6..c37f8fb 100644
--- a/erpnext/e_commerce/product_query.py
+++ b/erpnext/e_commerce/product_query.py
@@ -81,7 +81,7 @@
 				item.in_stock = "green" if stock_qty else "red"
 
 			item.wished = False
-			if frappe.db.exists("Wishlist Items", {"item_code": item.item_code}):
+			if frappe.db.exists("Wishlist Items", {"item_code": item.item_code, "parent": frappe.session.user}):
 				item.wished = True
 
 		return result
diff --git a/erpnext/public/js/shopping_cart.js b/erpnext/public/js/shopping_cart.js
index bcfa983..b57862b 100644
--- a/erpnext/public/js/shopping_cart.js
+++ b/erpnext/public/js/shopping_cart.js
@@ -186,5 +186,40 @@
 				$(".shopping-cart").toggleClass('hidden', r.message ? false : true);
 			}
 		});
+	},
+
+	animate_add_to_cart(button) {
+		// Create 'added to cart' animation
+		let btn_id = "#" + button[0].id;
+		this.toggle_button_class(button, 'not-added', 'added-to-cart');
+		$(btn_id).text('Added to Cart');
+
+		// undo
+		setTimeout(() => {
+			this.toggle_button_class(button, 'added-to-cart', 'not-added');
+			$(btn_id).text('Add to Cart');
+		}, 2000);
+	},
+
+	toggle_button_class(button, remove, add) {
+		button.removeClass(remove);
+		button.addClass(add);
+	},
+
+	bind_add_to_cart_action() {
+		$('.page_content').on('click', '.btn-add-to-cart-list', (e) => {
+			const $btn = $(e.currentTarget);
+			$btn.prop('disabled', true);
+
+			this.animate_add_to_cart($btn);
+
+			const item_code = $btn.data('item-code');
+			erpnext.shopping_cart.update_cart({
+				item_code,
+				qty: 1
+			});
+
+		});
 	}
+
 });
diff --git a/erpnext/public/js/wishlist.js b/erpnext/public/js/wishlist.js
index 328bdb9..6ab1906 100644
--- a/erpnext/public/js/wishlist.js
+++ b/erpnext/public/js/wishlist.js
@@ -1,13 +1,12 @@
-frappe.provide("erpnext.e_commerce");
-var wishlist = erpnext.e_commerce;
+frappe.provide("erpnext.wishlist");
+var wishlist = erpnext.wishlist;
 
-frappe.ready(function() {
-	$(".wishlist").toggleClass('hidden', true);
-	wishlist.set_wishlist_count();
-});
+frappe.provide("erpnext.shopping_cart");
+var shopping_cart = erpnext.shopping_cart;
 
 $.extend(wishlist, {
 	set_wishlist_count: function() {
+		// set badge count for wishlist icon
 		var wish_count = frappe.get_cookie("wish_count");
 		if(frappe.session.user==="Guest") {
 			wish_count = 0;
@@ -35,5 +34,127 @@
 		} else {
 			$badge.remove();
 		}
+	},
+
+	bind_move_to_cart_action: function() {
+		// move item to cart from wishlist
+		$('.page_content').on("click", ".btn-add-to-cart", (e) => {
+			const $move_to_cart_btn = $(e.currentTarget);
+			let item_code = $move_to_cart_btn.data("item-code");
+
+			shopping_cart.shopping_cart_update({
+				item_code,
+				qty: 1,
+				cart_dropdown: true
+			});
+
+			let success_action = function() {
+				const $card_wrapper = $move_to_cart_btn.closest(".item-card");
+				$card_wrapper.addClass("wish-removed");
+			};
+			let args = { item_code: item_code };
+			this.add_remove_from_wishlist("remove", args, success_action, null, true);
+		});
+	},
+
+	bind_remove_action: function() {
+		// remove item from wishlist
+		$('.page_content').on("click", ".remove-wish", (e) => {
+			const $remove_wish_btn = $(e.currentTarget);
+			let item_code = $remove_wish_btn.data("item-code");
+
+			let success_action = function() {
+				const $card_wrapper = $remove_wish_btn.closest(".item-card");
+				$card_wrapper.addClass("wish-removed");
+			};
+			let args = { item_code: item_code };
+			this.add_remove_from_wishlist("remove", args, success_action);
+		});
+	},
+
+	bind_wishlist_action() {
+		// 'wish'('like') or 'unwish' item in product listing
+		$('.page_content').on('click', '.like-action', (e) => {
+			const $btn = $(e.currentTarget);
+			const $wish_icon = $btn.find('.wish-icon');
+			let me = this;
+
+			let success_action = function() {
+				erpnext.wishlist.set_wishlist_count();
+			};
+
+			if ($wish_icon.hasClass('wished')) {
+				// un-wish item
+				$btn.removeClass("like-animate");
+				this.toggle_button_class($wish_icon, 'wished', 'not-wished');
+
+				let args = { item_code: $btn.data('item-code') };
+				let failure_action = function() {
+					me.toggle_button_class($wish_icon, 'not-wished', 'wished');
+				};
+				this.add_remove_from_wishlist("remove", args, success_action, failure_action);
+			} else {
+				// wish item
+				$btn.addClass("like-animate");
+				this.toggle_button_class($wish_icon, 'not-wished', 'wished');
+
+				let args = {
+					item_code: $btn.data('item-code'),
+					price: $btn.data('price'),
+					formatted_price: $btn.data('formatted-price')
+				};
+				let failure_action = function() {
+					me.toggle_button_class($wish_icon, 'wished', 'not-wished');
+				};
+				this.add_remove_from_wishlist("add", args, success_action, failure_action);
+			}
+		});
+	},
+
+	toggle_button_class(button, remove, add) {
+		button.removeClass(remove);
+		button.addClass(add);
+	},
+
+	add_remove_from_wishlist(action, args, success_action, failure_action, async=false) {
+		/*	AJAX call to add or remove Item from Wishlist
+			action: "add" or "remove"
+			args: args for method (item_code, price, formatted_price),
+			success_action: method to execute on successs,
+			failure_action: method to execute on failure,
+			async: make call asynchronously (true/false).	*/
+		let method = "erpnext.e_commerce.doctype.wishlist.wishlist.add_to_wishlist";
+		if (action === "remove") {
+			method = "erpnext.e_commerce.doctype.wishlist.wishlist.remove_from_wishlist";
+		}
+
+		frappe.call({
+			type: "POST",
+			method: method,
+			args: args,
+			callback: function (r) {
+				if (r.exc) {
+					if (failure_action && (typeof failure_action === 'function')) {failure_action();}
+					frappe.msgprint({
+						message: __("Sorry, something went wrong. Please refresh."),
+						indicator: "red", title: __("Note")
+					});
+				} else {
+					if (success_action && (typeof success_action === 'function')) {success_action();}
+				}
+			}
+		});
 	}
+
+});
+
+frappe.ready(function() {
+	if (window.location.pathname !== "/wishlist") {
+		$(".wishlist").toggleClass('hidden', true);
+		wishlist.set_wishlist_count();
+	} else {
+		wishlist.bind_move_to_cart_action();
+		wishlist.bind_remove_action();
+	}
+
 });
\ No newline at end of file
diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss
index 3d66f14..6a96e41 100644
--- a/erpnext/public/scss/shopping_cart.scss
+++ b/erpnext/public/scss/shopping_cart.scss
@@ -648,3 +648,27 @@
 		color: white;
 	}
 }
+
+.wishlist-cart-not-added {
+	color: var(--blue-500);
+	background-color: white;
+	border: 1px solid var(--blue-500);
+	--icon-stroke: var(--blue-500);
+
+	&:hover {
+		background-color: var(--blue-500);
+		color: white;
+		--icon-stroke: white;
+	}
+}
+
+.remove-wish {
+	&:hover {
+		background-color: var(--gray-100);
+		border: 1px solid var(--icon-stroke);
+	}
+}
+
+.wish-removed {
+	display: none;
+}
diff --git a/erpnext/templates/includes/macros.html b/erpnext/templates/includes/macros.html
index 743daaf..aec201e 100644
--- a/erpnext/templates/includes/macros.html
+++ b/erpnext/templates/includes/macros.html
@@ -118,7 +118,6 @@
 	'text-left': align == 'Left' or is_featured,
 }) -%}
 <div class="card-body {{ align_class }}" style="width:100%">
-
 	<div style="margin-top: 16px; display: flex;">
 		<a href="/{{ item.route or '#' }}">
 			<div class="product-title">{{ title or '' }}</div>
@@ -128,7 +127,9 @@
 		{% endif %}
 		{% if not item.has_variants %}
 			<div class="like-action"
-				data-item-code="{{ item.item_code }}" data-price="{{ item.price }}">
+				data-item-code="{{ item.item_code }}"
+				data-price="{{ item.price }}"
+				data-formatted-price="{{ item.get('formatted_price') }}">
 				<svg class="icon sm">
 					{%- set icon_class = "wished" if item.wished else "not-wished"-%}
 					<use class="{{ icon_class }} wish-icon" href="#icon-heart"></use>
@@ -161,3 +162,63 @@
 	{% endif %}
 </div>
 {%- endmacro -%}
+
+
+{%- macro wishlist_card(item, settings) %}
+<div class="col-sm-3 item-card" style="min-width: 220px;">
+	<div class="card text-center">
+		{% if item.image %}
+			<div class="card-img-container">
+				<a href="/{{ item.route or '#' }}" style="text-decoration: none;">
+					<img class="card-img" src="{{ item.image }}" alt="{{ title }}">
+				</a>
+				<div class="remove-wish"
+					style="position:absolute; top:10px; right: 20px; border-radius: 50%; border: 1px solid var(--gray-100); width: 25px; height: 25px;"
+					data-item-code="{{ item.item_code }}">
+					<span style="padding-bottom: 2px;">
+						<svg class="icon sm remove-wish-icon" style="margin-bottom: 4px; margin-left: 0.5px;">
+							<use class="close" href="#icon-close"></use>
+						</svg>
+					</span>
+				</div>
+
+			</div>
+		{% else %}
+		<a href="/{{ item.route or '#' }}" style="text-decoration: none;">
+			<div class="card-img-top no-image">
+				{{ frappe.utils.get_abbr(title) }}
+			</div>
+		</a>
+		{% endif %}
+
+		{{ wishlist_card_body(item, settings) }}
+
+
+	</div>
+</div>
+{%- endmacro -%}
+
+{%- macro wishlist_card_body(item, settings) %}
+<div class="card-body text-center" style="width:100%">
+	<div style="margin-top: 16px;">
+		<div class="product-title">{{ item.item_name or item.item_code or ''}}</div>
+	</div>
+	<div class="product-price">{{ item.formatted_price or '' }}</div>
+
+	{% if (item.available and settings.show_stock_availability) or (not settings.show_stock_availability) %}
+	<!-- Show move to cart button if in stock or if showing stock availability is disabled -->
+	<button data-item-code="{{ item.item_code}}" class="btn btn-add-to-cart w-100 wishlist-cart-not-added">
+		<span class="mr-2">
+			<svg class="icon icon-md">
+				<use href="#icon-assets"></use>
+			</svg>
+		</span>
+		{{ _("Move to Cart") }}
+	</button>
+	{% else %}
+	<div style="color: #F47A7A; width: 100%;">
+		{{ _("Not in Stock") }}
+	</div>
+	{% endif %}
+</div>
+{%- endmacro -%}
diff --git a/erpnext/templates/includes/navbar/navbar_items.html b/erpnext/templates/includes/navbar/navbar_items.html
index 54ed98a..793bacb 100644
--- a/erpnext/templates/includes/navbar/navbar_items.html
+++ b/erpnext/templates/includes/navbar/navbar_items.html
@@ -10,7 +10,7 @@
 		</a>
 	</li>
 	<li class="wishlist wishlist-icon hidden">
-		<a class="nav-link" href="/cart">
+		<a class="nav-link" href="/wishlist">
 			<svg class="icon icon-lg">
 				<use href="#icon-heart"></use>
 			</svg>
diff --git a/erpnext/templates/pages/wishlist.html b/erpnext/templates/pages/wishlist.html
new file mode 100644
index 0000000..6e7a65b
--- /dev/null
+++ b/erpnext/templates/pages/wishlist.html
@@ -0,0 +1,24 @@
+{% extends "templates/web.html" %}
+
+{% block title %} {{ _("Wishlist") }} {% endblock %}
+
+{% block header %}<h3 class="shopping-cart-header mt-2 mb-6">{{ _("Wishlist") }}</h1>{% endblock %}
+
+{% block page_content %}
+{% if items %}
+	<div class="row">
+		<div class="col-12 col-md-11 item-card-group-section">
+			<div class="row products-list">
+					{% from "erpnext/templates/includes/macros.html" import wishlist_card %}
+					{% for item in items %}
+						{{ wishlist_card(item, settings) }}
+					{% endfor %}
+			</div>
+		</div>
+	</div>
+{% else %}
+	<!-- TODO: Make empty state for wishlist -->
+	{% include "erpnext/www/all-products/not_found.html" %}
+{% endif %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/erpnext/templates/pages/wishlist.py b/erpnext/templates/pages/wishlist.py
new file mode 100644
index 0000000..15aef87
--- /dev/null
+++ b/erpnext/templates/pages/wishlist.py
@@ -0,0 +1,39 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+from __future__ import unicode_literals
+
+no_cache = 1
+
+import frappe
+from erpnext.e_commerce.shopping_cart.cart import get_cart_quotation
+
+def get_context(context):
+	settings = frappe.get_doc("E Commerce Settings")
+	items = get_wishlist_items()
+
+	if settings.show_stock_availability:
+		for item in items:
+			stock_qty = frappe.utils.flt(
+				frappe.db.get_value("Bin",
+					{
+						"item_code": item.item_code,
+						"warehouse": item.get("warehouse")
+					},
+					"actual_qty")
+				)
+			item.available = True if stock_qty else False
+
+	context.items = items
+	context.settings = settings
+
+def get_wishlist_items():
+	if frappe.db.exists("Wishlist", frappe.session.user):
+		return frappe.db.sql("""
+			Select
+				item_code, item_name, website_item, price,
+				warehouse, image, item_group, route, formatted_price
+			from
+				`tabWishlist Items`
+			where
+				parent=%(user)s"""%{"user": frappe.db.escape(frappe.session.user)}, as_dict=1)
+	return
\ No newline at end of file
diff --git a/erpnext/www/all-products/index.js b/erpnext/www/all-products/index.js
index fc1a3f4..3421709 100644
--- a/erpnext/www/all-products/index.js
+++ b/erpnext/www/all-products/index.js
@@ -73,98 +73,8 @@
 		}
 
 		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);
-
-				this.animate_add_to_cart($btn);
-
-				const item_code = $btn.data('item-code');
-				erpnext.shopping_cart.update_cart({
-					item_code,
-					qty: 1
-				});
-
-			});
-		}
-
-		animate_add_to_cart(button) {
-			// Create 'added to cart' animation
-			let btn_id = "#" + button[0].id;
-			this.toggle_button_class(button, 'not-added', 'added-to-cart');
-			$(btn_id).text('Added to Cart');
-
-			// undo
-			setTimeout(() => {
-				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);
+			erpnext.shopping_cart.bind_add_to_cart_action();
+			erpnext.wishlist.bind_wishlist_action();
 		}
 
 		bind_search() {