Merge pull request #15247 from frappe/hub-redesign

[encore] MarketPlace
diff --git a/erpnext/demo/data/drug_list.json b/erpnext/demo/data/drug_list.json
index f34ca57..9b101cb 100644
--- a/erpnext/demo/data/drug_list.json
+++ b/erpnext/demo/data/drug_list.json
@@ -48,7 +48,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -133,7 +132,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -218,7 +216,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -303,7 +300,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -388,7 +384,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -473,7 +468,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -558,7 +552,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -643,7 +636,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -728,7 +720,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -813,7 +804,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -898,7 +888,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -983,7 +972,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -1068,7 +1056,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -1153,7 +1140,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -1238,7 +1224,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -1323,7 +1308,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -1408,7 +1392,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -1493,7 +1476,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -1578,7 +1560,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -1663,7 +1644,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -1748,7 +1728,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -1833,7 +1812,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -1918,7 +1896,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -2003,7 +1980,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -2088,7 +2064,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -2173,7 +2148,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -2258,7 +2232,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -2343,7 +2316,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -2428,7 +2400,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -2513,7 +2484,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -2598,7 +2568,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -2683,7 +2652,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -2768,7 +2736,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -2853,7 +2820,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -2938,7 +2904,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -3023,7 +2988,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -3108,7 +3072,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -3193,7 +3156,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -3278,7 +3240,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -3363,7 +3324,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -3448,7 +3408,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -3533,7 +3492,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -3618,7 +3576,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -3703,7 +3660,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -3788,7 +3744,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -3873,7 +3828,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -3958,7 +3912,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -4043,7 +3996,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -4128,7 +4080,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -4213,7 +4164,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -4298,7 +4248,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -4383,7 +4332,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -4468,7 +4416,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -4553,7 +4500,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -4638,7 +4584,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -4723,7 +4668,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -4808,7 +4752,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -4893,7 +4836,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -4978,7 +4920,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -5063,7 +5004,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -5148,7 +5088,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -5233,7 +5172,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
@@ -5318,7 +5256,6 @@
   "naming_series": null,
   "net_weight": 0.0,
   "opening_stock": 0.0,
-  "publish_in_hub": 1,
   "quality_parameters": [],
   "reorder_levels": [],
   "route": null,
diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py
index 65b1386..0ebbec7 100644
--- a/erpnext/hub_node/__init__.py
+++ b/erpnext/hub_node/__init__.py
@@ -2,10 +2,7 @@
 # For license information, please see license.txt
 
 from __future__ import unicode_literals
-import frappe, requests, json
-from frappe.utils import now, nowdate, cint
-from frappe.utils.nestedset import get_root_of
-from frappe.contacts.doctype.contact.contact import get_default_contact
+import frappe
 
 @frappe.whitelist()
 def enable_hub():
@@ -15,263 +12,6 @@
 	return hub_settings
 
 @frappe.whitelist()
-def get_list(doctype, start=0, limit=20, fields=["*"], filters="{}", order_by=None):
-	connection = get_client_connection()
-	filters = json.loads(filters)
-
-	response = connection.get_list(doctype,
-		limit_start=start, limit_page_length=limit,
-		filters=filters, fields=fields)
-
-	# Bad, need child tables in response
-	listing = []
-	for obj in response:
-		doc = connection.get_doc(doctype, obj['name'])
-		listing.append(doc)
-
-	return listing
-
-@frappe.whitelist()
-def get_item_favourites(start=0, limit=20, fields=["*"], order_by=None):
-	doctype = 'Hub Item'
+def sync():
 	hub_settings = frappe.get_doc('Hub Settings')
-	item_names_str = hub_settings.get('custom_data') or '[]'
-	item_names = json.loads(item_names_str)
-	filters = json.dumps({
-		'hub_item_code': ['in', item_names]
-	})
-	return get_list(doctype, start, limit, fields, filters, order_by)
-
-@frappe.whitelist()
-def update_wishlist_item(item_name, remove=0):
-	remove = int(remove)
-	hub_settings = frappe.get_doc('Hub Settings')
-	data = hub_settings.get('custom_data')
-	if not data or not json.loads(data):
-		data = '[]'
-		hub_settings.custom_data = data
-		hub_settings.save()
-
-	item_names_str = data
-	item_names = json.loads(item_names_str)
-	if not remove and item_name not in item_names:
-		item_names.append(item_name)
-	if remove and item_name in item_names:
-		item_names.remove(item_name)
-
-	item_names_str = json.dumps(item_names)
-
-	hub_settings.custom_data = item_names_str
-	hub_settings.save()
-
-@frappe.whitelist()
-def get_meta(doctype):
-	connection = get_client_connection()
-	meta = connection.get_doc('DocType', doctype)
-	categories = connection.get_list('Hub Category',
-		limit_start=0, limit_page_length=300,
-		filters={}, fields=['name'])
-
-	categories = [d.get('name') for d in categories]
-	return {
-		'meta': meta,
-		'companies': connection.get_list('Hub Company',
-			limit_start=0, limit_page_length=300,
-			filters={}, fields=['name']),
-		'categories': categories
-	}
-
-@frappe.whitelist()
-def get_categories(parent='All Categories'):
-	# get categories info with parent category and stuff
-	connection = get_client_connection()
-	categories = connection.get_list('Hub Category', filters={'parent_hub_category': parent})
-
-	response = [{'value': c.get('name'), 'expandable': c.get('is_group')} for c in categories]
-	return response
-
-@frappe.whitelist()
-def update_category(hub_item_code, category):
-	connection = get_hub_connection()
-
-	# args = frappe._dict(dict(
-	# 	doctype='Hub Category',
-	# 	hub_category_name=category
-	# ))
-	# response = connection.insert('Hub Category', args)
-
-	response = connection.update('Hub Item', frappe._dict(dict(
-		doctype='Hub Item',
-		hub_category = category
-	)), hub_item_code)
-
-	return response
-
-@frappe.whitelist()
-def send_review(hub_item_code, review):
-	review = json.loads(review)
-	hub_connection = get_hub_connection()
-
-	item_doc = hub_connection.connection.get_doc('Hub Item', hub_item_code)
-	existing_reviews = item_doc.get('reviews')
-
-	reviews = [review]
-	review.setdefault('idx', 0)
-	for r in existing_reviews:
-		if r.get('user') != review.get('user'):
-			reviews.append(r)
-
-	response = hub_connection.update('Hub Item', dict(
-		doctype='Hub Item',
-		reviews = reviews
-	), hub_item_code)
-
-	return response
-
-@frappe.whitelist()
-def get_details(hub_sync_id=None, doctype='Hub Item'):
-	if not hub_sync_id:
-		return
-	connection = get_client_connection()
-	details = connection.get_doc(doctype, hub_sync_id)
-	reviews = details.get('reviews')
-	if reviews and len(reviews):
-		for r in reviews:
-			r.setdefault('pretty_date', frappe.utils.pretty_date(r.get('modified')))
-		details.setdefault('reviews', reviews)
-	return details
-
-def get_client_connection():
-	# frappeclient connection
-	hub_connection = get_hub_connection()
-	return hub_connection.connection
-
-def get_hub_connection():
-	hub_connector = frappe.get_doc(
-		'Data Migration Connector', 'Hub Connector')
-	hub_connection = hub_connector.get_connection()
-	return hub_connection
-
-def make_opportunity(buyer_name, email_id):
-	buyer_name = "HUB-" + buyer_name
-
-	if not frappe.db.exists('Lead', {'email_id': email_id}):
-		lead = frappe.new_doc("Lead")
-		lead.lead_name = buyer_name
-		lead.email_id = email_id
-		lead.save(ignore_permissions=True)
-
-	o = frappe.new_doc("Opportunity")
-	o.enquiry_from = "Lead"
-	o.lead = frappe.get_all("Lead", filters={"email_id": email_id}, fields = ["name"])[0]["name"]
-	o.save(ignore_permissions=True)
-
-@frappe.whitelist()
-def make_rfq_and_send_opportunity(item, supplier):
-	supplier = make_supplier(supplier)
-	contact = make_contact(supplier)
-	item = make_item(item)
-	rfq = make_rfq(item, supplier, contact)
-	status = send_opportunity(contact)
-
-	return {
-		'rfq': rfq,
-		'hub_document_created': status
-	}
-
-def make_supplier(supplier):
-	# make supplier if not already exists
-	supplier = frappe._dict(json.loads(supplier))
-
-	if not frappe.db.exists('Supplier', {'supplier_name': supplier.supplier_name}):
-		supplier_doc = frappe.get_doc({
-			'doctype': 'Supplier',
-			'supplier_name': supplier.supplier_name,
-			'supplier_group': supplier.supplier_group,
-			'supplier_email': supplier.supplier_email
-		}).insert()
-	else:
-		supplier_doc = frappe.get_doc('Supplier', supplier.supplier_name)
-
-	return supplier_doc
-
-def make_contact(supplier):
-	contact_name = get_default_contact('Supplier', supplier.supplier_name)
-	# make contact if not already exists
-	if not contact_name:
-		contact = frappe.get_doc({
-			'doctype': 'Contact',
-			'first_name': supplier.supplier_name,
-			'email_id': supplier.supplier_email,
-			'is_primary_contact': 1,
-			'links': [
-				{'link_doctype': 'Supplier', 'link_name': supplier.supplier_name}
-			]
-		}).insert()
-	else:
-		contact = frappe.get_doc('Contact', contact_name)
-
-	return contact
-
-def make_item(item):
-	# make item if not already exists
-	item = frappe._dict(json.loads(item))
-
-	if not frappe.db.exists('Item', {'item_code': item.item_code}):
-		item_doc = frappe.get_doc({
-			'doctype': 'Item',
-			'item_code': item.item_code,
-			'item_group': item.item_group,
-			'is_item_from_hub': 1
-		}).insert()
-	else:
-		item_doc = frappe.get_doc('Item', item.item_code)
-
-	return item_doc
-
-def make_rfq(item, supplier, contact):
-	# make rfq
-	rfq = frappe.get_doc({
-		'doctype': 'Request for Quotation',
-		'transaction_date': nowdate(),
-		'status': 'Draft',
-		'company': frappe.db.get_single_value('Hub Settings', 'company'),
-		'message_for_supplier': 'Please supply the specified items at the best possible rates',
-		'suppliers': [
-			{ 'supplier': supplier.name, 'contact': contact.name }
-		],
-		'items': [
-			{
-				'item_code': item.item_code,
-				'qty': 1,
-				'schedule_date': nowdate(),
-				'warehouse': item.default_warehouse or get_root_of("Warehouse"),
-				'description': item.description,
-				'uom': item.stock_uom
-			}
-		]
-	}).insert()
-
-	rfq.save()
-	rfq.submit()
-	return rfq
-
-def send_opportunity(contact):
-	# Make Hub Message on Hub with lead data
-	doc = {
-		'doctype': 'Lead',
-		'lead_name': frappe.db.get_single_value('Hub Settings', 'company'),
-		'email_id': frappe.db.get_single_value('Hub Settings', 'user')
-	}
-
-	args = frappe._dict(dict(
-		doctype='Hub Message',
-		reference_doctype='Lead',
-		data=json.dumps(doc),
-		user=contact.email_id
-	))
-
-	connection = get_hub_connection()
-	response = connection.insert('Hub Message', args)
-
-	return response.ok
+	hub_settings.sync()
diff --git a/erpnext/hub_node/api.py b/erpnext/hub_node/api.py
new file mode 100644
index 0000000..be81eff
--- /dev/null
+++ b/erpnext/hub_node/api.py
@@ -0,0 +1,169 @@
+from __future__ import unicode_literals
+import frappe, json
+import io, base64, os, requests
+from frappe.frappeclient import FrappeClient
+from frappe.desk.form.load import get_attachments
+from frappe.utils.file_manager import get_file_path
+from six import string_types
+
+@frappe.whitelist()
+def call_hub_method(method, params=None):
+	connection = get_hub_connection()
+
+	if isinstance(params, string_types):
+		params = json.loads(params)
+
+	params.update({
+		'cmd': 'hub.hub.api.' + method
+	})
+
+	response = connection.post_request(params)
+	return response
+
+def map_fields(items):
+	field_mappings = get_field_mappings()
+	table_fields = [d.fieldname for d in frappe.get_meta('Item').get_table_fields()]
+
+	hub_seller = frappe.db.get_value('Hub Settings' , 'Hub Settings', 'company_email')
+
+	for item in items:
+		for fieldname in table_fields:
+			item.pop(fieldname, None)
+
+		for mapping in field_mappings:
+			local_fieldname = mapping.get('local_fieldname')
+			remote_fieldname = mapping.get('remote_fieldname')
+
+			value = item.get(local_fieldname)
+			item.pop(local_fieldname, None)
+			item[remote_fieldname] = value
+
+		item['doctype'] = 'Hub Item'
+		item['hub_seller'] = hub_seller
+		item.pop('attachments', None)
+
+	return items
+
+@frappe.whitelist()
+def get_valid_items(search_value=''):
+	items = frappe.get_list(
+		'Item',
+		fields=["*"],
+		filters={
+			'item_name': ['like', '%' + search_value + '%'],
+			'publish_in_hub': 0
+		},
+		order_by="modified desc"
+	)
+
+	valid_items = filter(lambda x: x.image and x.description, items)
+
+	def prepare_item(item):
+		item.source_type = "local"
+		item.attachments = get_attachments('Item', item.item_code)
+		return item
+
+	valid_items = map(prepare_item, valid_items)
+
+	return valid_items
+
+@frappe.whitelist()
+def publish_selected_items(items_to_publish):
+	items_to_publish = json.loads(items_to_publish)
+	if not len(items_to_publish):
+		frappe.throw('No items to publish')
+
+	for item in items_to_publish:
+		item_code = item.get('item_code')
+		frappe.db.set_value('Item', item_code, 'publish_in_hub', 1)
+
+		frappe.get_doc({
+			'doctype': 'Hub Tracked Item',
+			'item_code': item_code,
+			'hub_category': item.get('hub_category'),
+			'image_list': item.get('image_list')
+		}).insert(ignore_if_duplicate=True)
+
+
+	items = map_fields(items_to_publish)
+
+	try:
+		item_sync_preprocess(len(items))
+		load_base64_image_from_items(items)
+
+		# TODO: Publish Progress
+		connection = get_hub_connection()
+		connection.insert_many(items)
+
+		item_sync_postprocess()
+	except Exception as e:
+		frappe.log_error(message=e, title='Hub Sync Error')
+
+def item_sync_preprocess(intended_item_publish_count):
+	response = call_hub_method('pre_items_publish', {
+		'intended_item_publish_count': intended_item_publish_count
+	})
+
+	if response:
+		frappe.db.set_value("Hub Settings", "Hub Settings", "sync_in_progress", 1)
+		return response
+	else:
+		frappe.throw('Unable to update remote activity')
+
+def item_sync_postprocess():
+	response = call_hub_method('post_items_publish', {})
+	if response:
+		frappe.db.set_value('Hub Settings', 'Hub Settings', 'last_sync_datetime', frappe.utils.now())
+	else:
+		frappe.throw('Unable to update remote activity')
+
+	frappe.db.set_value('Hub Settings', 'Hub Settings', 'sync_in_progress', 0)
+
+
+def load_base64_image_from_items(items):
+	for item in items:
+		file_path = item['image']
+		file_name = os.path.basename(file_path)
+		base64content = None
+
+		if file_path.startswith('http'):
+			# fetch content and then base64 it
+			url = file_path
+			response = requests.get(url)
+			base64content = base64.b64encode(response.content)
+		else:
+			# read file then base64 it
+			file_path = os.path.abspath(get_file_path(file_path))
+			with io.open(file_path, 'rb') as f:
+				base64content = base64.b64encode(f.read())
+
+		image_data = json.dumps({
+			'file_name': file_name,
+			'base64': base64content
+		})
+
+		item['image'] = image_data
+
+
+def get_hub_connection():
+	read_only = True
+
+	if frappe.db.exists('Data Migration Connector', 'Hub Connector'):
+		hub_connector = frappe.get_doc('Data Migration Connector', 'Hub Connector')
+
+		# full rights to user who registered as hub_seller
+		if hub_connector.username == frappe.session.user:
+			read_only = False
+
+		if not read_only:
+			hub_connection = hub_connector.get_connection()
+			return hub_connection.connection
+
+	# read-only connection
+	if read_only:
+		hub_url = frappe.db.get_single_value('Hub Settings', 'hub_url')
+		hub_connection = FrappeClient(hub_url)
+		return hub_connection
+
+def get_field_mappings():
+	return []
diff --git a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json
index 7423f2e..bcece69 100644
--- a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json
+++ b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json
@@ -1,55 +1,55 @@
 {
- "condition": "{\"publish_in_hub\": 1}",
- "creation": "2017-09-07 13:27:52.726350",
- "docstatus": 0,
- "doctype": "Data Migration Mapping",
+ "condition": "{\"publish_in_hub\": 1}", 
+ "creation": "2017-09-07 13:27:52.726350", 
+ "docstatus": 0, 
+ "doctype": "Data Migration Mapping", 
  "fields": [
   {
-   "is_child_table": 0,
-   "local_fieldname": "item_code",
+   "is_child_table": 0, 
+   "local_fieldname": "item_code", 
    "remote_fieldname": "item_code"
-  },
+  }, 
   {
-   "is_child_table": 0,
-   "local_fieldname": "item_name",
+   "is_child_table": 0, 
+   "local_fieldname": "item_name", 
    "remote_fieldname": "item_name"
-  },
+  }, 
   {
-   "is_child_table": 0,
-   "local_fieldname": "eval:frappe.db.get_default(\"company\")",
-   "remote_fieldname": "company_name"
-  },
+   "is_child_table": 0, 
+   "local_fieldname": "eval:frappe.db.get_value('Hub Settings' , 'Hub Settings', 'company_email')", 
+   "remote_fieldname": "hub_seller"
+  }, 
   {
-   "is_child_table": 0,
-   "local_fieldname": "image",
+   "is_child_table": 0, 
+   "local_fieldname": "image", 
    "remote_fieldname": "image"
-  },
+  }, 
   {
-   "is_child_table": 0,
-   "local_fieldname": "item_group",
+   "is_child_table": 0, 
+   "local_fieldname": "image_list", 
+   "remote_fieldname": "image_list"
+  }, 
+  {
+   "is_child_table": 0, 
+   "local_fieldname": "item_group", 
    "remote_fieldname": "item_group"
-  },
+  }, 
   {
-   "is_child_table": 0,
-   "local_fieldname": "eval:frappe.session.user",
-   "remote_fieldname": "seller"
-  },
-  {
-   "is_child_table": 0,
-   "local_fieldname": "eval:frappe.db.get_default(\"country\")",
-   "remote_fieldname": "country"
+   "is_child_table": 0, 
+   "local_fieldname": "hub_category", 
+   "remote_fieldname": "hub_category"
   }
- ],
- "idx": 1,
- "local_doctype": "Item",
- "mapping_name": "Item to Hub Item",
- "mapping_type": "Push",
- "migration_id_field": "hub_sync_id",
- "modified": "2018-02-14 15:57:05.595712",
- "modified_by": "achilles@erpnext.com",
- "name": "Item to Hub Item",
- "owner": "Administrator",
- "page_length": 10,
- "remote_objectname": "Hub Item",
+ ], 
+ "idx": 1, 
+ "local_doctype": "Item", 
+ "mapping_name": "Item to Hub Item", 
+ "mapping_type": "Push", 
+ "migration_id_field": "hub_sync_id", 
+ "modified": "2018-08-19 22:20:25.727581", 
+ "modified_by": "Administrator", 
+ "name": "Item to Hub Item", 
+ "owner": "Administrator", 
+ "page_length": 10, 
+ "remote_objectname": "Hub Item", 
  "remote_primary_key": "item_code"
 }
\ No newline at end of file
diff --git a/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json b/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json
index d66ac24..e90b1dd 100644
--- a/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json
+++ b/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json
@@ -1,22 +1,19 @@
 {
- "creation": "2017-09-07 11:39:38.445902",
- "docstatus": 0,
- "doctype": "Data Migration Plan",
- "idx": 1,
+ "creation": "2017-09-07 11:39:38.445902", 
+ "docstatus": 0, 
+ "doctype": "Data Migration Plan", 
+ "idx": 1, 
  "mappings": [
   {
-   "enabled": 1,
+   "enabled": 1, 
    "mapping": "Item to Hub Item"
-  },
-  {
-   "enabled": 1,
-   "mapping": "Hub Message to Lead"
   }
- ],
- "modified": "2018-02-14 15:57:05.519715",
- "modified_by": "achilles@erpnext.com",
- "module": "Hub Node",
- "name": "Hub Sync",
- "owner": "Administrator",
- "plan_name": "Hub Sync"
+ ], 
+ "modified": "2018-08-19 22:20:25.644602", 
+ "modified_by": "Administrator", 
+ "module": "Hub Node", 
+ "name": "Hub Sync", 
+ "owner": "Administrator", 
+ "plan_name": "Hub Sync", 
+ "postprocess_method": "erpnext.hub_node.api.item_sync_postprocess"
 }
\ No newline at end of file
diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.js b/erpnext/hub_node/doctype/hub_settings/hub_settings.js
index 29d870b..089f499 100644
--- a/erpnext/hub_node/doctype/hub_settings/hub_settings.js
+++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.js
@@ -1,177 +1,3 @@
 frappe.ui.form.on("Hub Settings", {
-	refresh: function(frm) {
-		frm.add_custom_button(__('Logs'),
-			() => frappe.set_route('List', 'Data Migration Run', {
-				data_migration_plan: 'Hub Sync'
-			}));
-
-		frm.trigger("enabled");
-
-		if (frm.doc.enabled) {
-			frm.add_custom_button(__('Sync'),
-				() => frm.call('sync'));
-		}
-	},
-	onload: function(frm) {
-		let token = frappe.urllib.get_arg("access_token");
-		if(token) {
-			let email = frm.get_field("user");
-			console.log('token', frappe.urllib.get_arg("access_token"));
-
-			get_user_details(frm, token, email);
-			let row = frappe.model.add_child(frm.doc, "Hub Users", "users");
-			row.user = frappe.session.user;
-		}
-
-		if(!frm.doc.country) {
-			frm.set_value("country", frappe.defaults.get_default("Country"));
-		}
-		if(!frm.doc.company) {
-			frm.set_value("company", frappe.defaults.get_default("Company"));
-		}
-		if(!frm.doc.user) {
-			frm.set_value("user", frappe.session.user);
-		}
-	},
-	onload_post_render: function(frm) {
-		if(frm.get_field("unregister_from_hub").$input)
-			frm.get_field("unregister_from_hub").$input.addClass("btn-danger");
-	},
-	on_update: function(frm) {
-	},
-	enabled: function(frm) {
-		if(!frm.doc.enabled) {
-			frm.trigger("set_enable_hub_primary_button");
-		} else {
-			frm.page.set_primary_action(__("Save Settings"), () => {
-				frm.save();
-			});
-		}
-	},
-
-	hub_user_email: function(frm) {
-		if(frm.doc.hub_user_email){
-			frm.set_value("hub_user_name", frappe.user.full_name(frm.doc.hub_user_email));
-		}
-	},
-
-	set_enable_hub_primary_button: (frm) => {
-		frm.page.set_primary_action(__("Enable Hub"), () => {
-			if(frappe.session.user === "Administrator") {
-				frappe.msgprint(__("Please login as another user."))
-			} else {
-				// frappe.verify_password(() => {
-
-				// } );
-
-				frm.trigger("call_pre_reg");
-				// frm.trigger("call_register");
-
-			}
-		});
-	},
-
-	call_pre_reg: (frm) => {
-		this.frm.call({
-			doc: this.frm.doc,
-			method: "pre_reg",
-			args: {},
-			freeze: true,
-			callback: function(r) {
-				console.log(r.message);
-				authorize(frm, r.message.client_id, r.message.redirect_uri);
-			},
-			onerror: function() {
-				frappe.msgprint(__("Wrong Password"));
-				frm.set_value("enabled", 0);
-			}
-		});
-	},
-
-	call_register: (frm) => {
-		this.frm.call({
-			doc: this.frm.doc,
-			method: "register",
-			args: {},
-			freeze: true,
-			callback: function(r) {},
-			onerror: function() {
-				frappe.msgprint(__("Wrong Password"));
-				frm.set_value("enabled", 0);
-			}
-		});
-	},
-
-	unregister_from_hub: (frm) => {
-		frappe.verify_password(() => {
-			var d = frappe.confirm(__('Are you sure you want to unregister?'), () => {
-				frm.call('unregister');
-			}, () => {}, __('Confirm Action'));
-			d.get_primary_btn().addClass("btn-danger");
-		});
-	},
+	onload_post_render: function() {},
 });
-
-// let hub_url = 'https://hubmarket.org'
-let hub_url = 'http://159.89.175.122'
-// let hub_url = 'http://erpnext.hub:8000'
-
-function authorize(frm, client_id, redirect_uri) {
-
-    // queryStringData is details of OAuth Client (Implicit Grant) on Custom App
-	var queryStringData = {
-		response_type : "token",
-		client_id : client_id,
-        redirect_uri : redirect_uri
-	}
-
-    // Get current raw route and build url
-    const route = "/desk#" + frappe.get_raw_route_str();
-    localStorage.removeItem("route");  // Clear previously set route if any
-	localStorage.setItem("route", route);
-
-	// Go authorize!
-	let api_route = "/api/method/frappe.integrations.oauth2.authorize?";
-	let url = hub_url + api_route + $.param(queryStringData);
-	window.location.replace(url, 'test');
-}
-
-function get_user_details(frm, token, email) {
-	console.log('user_details');
-    var route = localStorage.getItem("route");
-    if (token && route) {
-        // Clean up access token from route
-		frappe.set_route(frappe.get_route().join("/"))
-
-        // query protected resource e.g. Hub Items with token
-        var call = {
-            "async": true,
-            "crossDomain": true,
-            "url": hub_url + "/api/resource/User",
-			"method": "GET",
-			"data": {
-				// "email": email,
-				"fields": '["name", "first_name", "language"]',
-				"limit_page_length": 1
-			},
-            "headers": {
-                "authorization": "Bearer " + token,
-                "content-type": "application/x-www-form-urlencoded"
-            }
-		}
-        $.ajax(call).done(function (response) {
-			// display openid profile
-			console.log('response', response);
-
-			let data = response.data[0];
-			frm.set_value("enabled", 1);
-			frm.set_value("hub_username", data.first_name);
-			frm.set_value("hub_user_status", "Starter");
-			frm.set_value("language", data.language);
-			frm.save();
-
-            // clear route from localStorage
-            localStorage.removeItem("route");
-        });
-    }
-}
diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.json b/erpnext/hub_node/doctype/hub_settings/hub_settings.json
index 7c7109c..e230515 100644
--- a/erpnext/hub_node/doctype/hub_settings/hub_settings.json
+++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.json
@@ -14,74 +14,13 @@
  "fields": [
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
-   "fieldname": "enabled", 
-   "fieldtype": "Check", 
-   "hidden": 1, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Enabled", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "suspended", 
-   "fieldtype": "Check", 
-   "hidden": 1, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Suspended", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "enabled", 
-   "fieldname": "hub_username", 
+   "default": "https://hubmarket.org", 
+   "fieldname": "hub_url", 
    "fieldtype": "Data", 
    "hidden": 0, 
    "ignore_user_permissions": 0, 
@@ -90,70 +29,7 @@
    "in_global_search": 0, 
    "in_list_view": 0, 
    "in_standard_filter": 0, 
-   "label": "Hub Username", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "user", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "User", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "User", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_0", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "", 
+   "label": "Hub URL", 
    "length": 0, 
    "no_copy": 0, 
    "permlevel": 0, 
@@ -171,108 +47,12 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
-   "depends_on": "enabled", 
-   "fieldname": "hub_user_status", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Status", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "enabled", 
-   "fieldname": "language", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Language", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 1, 
-   "collapsible_depends_on": "eval:(!doc.enabled)", 
-   "columns": 0, 
-   "depends_on": "", 
-   "fieldname": "seller_profile_section", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Company and Seller Profile", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "company_registered", 
+   "fieldname": "registered", 
    "fieldtype": "Check", 
    "hidden": 0, 
    "ignore_user_permissions": 0, 
@@ -281,14 +61,14 @@
    "in_global_search": 0, 
    "in_list_view": 0, 
    "in_standard_filter": 0, 
-   "label": "Company Registered", 
+   "label": "Registered", 
    "length": 0, 
    "no_copy": 0, 
    "permlevel": 0, 
    "precision": "", 
    "print_hide": 0, 
    "print_hide_if_no_value": 0, 
-   "read_only": 1, 
+   "read_only": 0, 
    "remember_last_selected_value": 0, 
    "report_hide": 0, 
    "reqd": 0, 
@@ -299,6 +79,39 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "sync_in_progress", 
+   "fieldtype": "Check", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Sync in Progress", 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "translatable": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -331,6 +144,7 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -362,6 +176,39 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "site_name", 
+   "fieldtype": "Data", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Site Name", 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 1, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "translatable": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -394,11 +241,44 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
-   "fieldname": "company_logo", 
+   "fieldname": "currency", 
+   "fieldtype": "Currency", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Currency", 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "translatable": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "logo", 
    "fieldtype": "Attach Image", 
    "hidden": 0, 
    "ignore_user_permissions": 0, 
@@ -425,11 +305,12 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
-   "fieldname": "seller_description", 
+   "fieldname": "company_description", 
    "fieldtype": "Text Editor", 
    "hidden": 0, 
    "ignore_user_permissions": 0, 
@@ -456,234 +337,12 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
-   "fieldname": "users_sb", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Enabled Users", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "users", 
-   "fieldtype": "Table", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Users", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Hub Users", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "enabled", 
-   "fieldname": "publish_section", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Publish", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "publish", 
-   "fieldtype": "Check", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Publish Items to Hub", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "publish", 
-   "fieldname": "publish_pricing", 
-   "fieldtype": "Check", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Publish Pricing", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "eval:(doc.publish && doc.publish_pricing)", 
-   "fieldname": "selling_price_list", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Selling Price List", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Price List", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "publish", 
-   "fieldname": "publish_availability", 
-   "fieldtype": "Check", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Publish Availability", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "publish", 
+   "depends_on": "", 
    "fieldname": "last_sync_datetime", 
    "fieldtype": "Datetime", 
    "hidden": 0, 
@@ -700,7 +359,7 @@
    "precision": "", 
    "print_hide": 0, 
    "print_hide_if_no_value": 0, 
-   "read_only": 1, 
+   "read_only": 0, 
    "remember_last_selected_value": 0, 
    "report_hide": 0, 
    "reqd": 0, 
@@ -711,6 +370,7 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -744,6 +404,7 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 1, 
@@ -777,6 +438,7 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -817,8 +479,8 @@
  "issingle": 1, 
  "istable": 0, 
  "max_attachments": 0, 
- "modified": "2018-03-26 00:55:17.929140", 
- "modified_by": "test1@example.com", 
+ "modified": "2018-08-29 17:46:30.413159", 
+ "modified_by": "Administrator", 
  "module": "Hub Node", 
  "name": "Hub Settings", 
  "name_case": "", 
@@ -826,7 +488,6 @@
  "permissions": [
   {
    "amend": 0, 
-   "apply_user_permissions": 0, 
    "cancel": 0, 
    "create": 1, 
    "delete": 0, 
@@ -852,5 +513,6 @@
  "sort_field": "modified", 
  "sort_order": "DESC", 
  "track_changes": 1, 
- "track_seen": 0
+ "track_seen": 0, 
+ "track_views": 0
 }
\ No newline at end of file
diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.py b/erpnext/hub_node/doctype/hub_settings/hub_settings.py
index 15ee4b7..ab0a7d2 100644
--- a/erpnext/hub_node/doctype/hub_settings/hub_settings.py
+++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.py
@@ -2,7 +2,7 @@
 # For license information, please see license.txt
 
 from __future__ import unicode_literals
-import frappe, requests, json
+import frappe, requests, json, time
 
 from frappe.model.document import Document
 from frappe.utils import add_years, now, get_datetime, get_datetime_str
@@ -10,107 +10,65 @@
 from erpnext.utilities.product import get_price, get_qty_in_stock
 from six import string_types
 
-hub_url = "https://hubmarket.org"
-# hub_url = "http://159.89.175.122"
-# hub_url = "http://erpnext.hub:8000"
-
-class OAuth2Session():
-	def __init__(self, headers):
-		self.headers = headers
-	def get(self, url, params, headers, verify):
-		res = requests.get(url, params=params, headers=self.headers, verify=verify)
-		return res
-	def post(self, url, data, verify):
-		res = requests.post(url, data=data, headers=self.headers, verify=verify)
-		return res
-	def put(self, url, data, verify):
-		res = requests.put(url, data=data, headers=self.headers, verify=verify)
-		return res
-
 class HubSetupError(frappe.ValidationError): pass
 
 class HubSettings(Document):
 
 	def validate(self):
-		if self.publish_pricing and not self.selling_price_list:
-			frappe.throw(_("Please select a Price List to publish pricing"))
+		self.site_name = frappe.utils.get_url()
 
 	def get_hub_url(self):
-		return hub_url
-
-	def sync(self):
-		"""Create and execute Data Migration Run for Hub Sync plan"""
-		frappe.has_permission('Hub Settings', throw=True)
-
-		doc = frappe.get_doc({
-			'doctype': 'Data Migration Run',
-			'data_migration_plan': 'Hub Sync',
-			'data_migration_connector': 'Hub Connector'
-		}).insert()
-
-		doc.run()
-
-	def pre_reg(self):
-		site_name = frappe.local.site + ':' + str(frappe.conf.webserver_port)
-		protocol = 'http://'
-		route = '/token'
-		data = {
-			'site_name': site_name,
-			'protocol': protocol,
-			'route': route
-		}
-
-		redirect_url = protocol + site_name + route
-		post_url = hub_url + '/api/method/hub.hub.api.pre_reg'
-
-		response = requests.post(post_url, data=data)
-		response.raise_for_status()
-		message = response.json().get('message')
-
-		if message and message.get('client_id'):
-			print("======CLIENT_ID======")
-			print(message.get('client_id'))
-
-			return {
-				'client_id': message.get('client_id'),
-				'redirect_uri': redirect_url
-			}
-
+		return self.hub_url
 
 	def register(self):
 		""" Create a User on hub.erpnext.org and return username/password """
+
+		if frappe.session.user == 'Administrator':
+			frappe.throw(_('Please login as another user to register on Marketplace'))
+
+		if 'System Manager' not in frappe.get_roles():
+			frappe.throw(_('Only users with System Manager role can register on Marketplace'), frappe.PermissionError)
+
+		self.site_name = frappe.utils.get_url()
+
 		data = {
-			'email': frappe.session.user
+			'profile': self.as_json()
 		}
-		post_url = hub_url + '/api/method/hub.hub.api.register'
+		post_url = self.get_hub_url() + '/api/method/hub.hub.api.register'
 
-		response = requests.post(post_url, data=data)
+		response = requests.post(post_url, data=data, headers = {'accept': 'application/json'})
+
 		response.raise_for_status()
-		message = response.json().get('message')
 
-		if message and message.get('password'):
-			self.user = frappe.session.user
+		if response.ok:
+			message = response.json().get('message')
+		else:
+			frappe.throw(json.loads(response.text))
+
+		if message.get('email'):
 			self.create_hub_connector(message)
-			self.company = frappe.defaults.get_user_default('company')
-			self.enabled = 1
+			self.registered = 1
 			self.save()
 
-	def unregister(self):
-		""" Disable the User on hub.erpnext.org"""
+		return message or None
 
-		hub_connector = frappe.get_doc(
-			'Data Migration Connector', 'Hub Connector')
+	# def unregister(self):
+	# 	""" Disable the User on hub.erpnext.org"""
 
-		connection = hub_connector.get_connection()
-		response_doc = connection.update('User', frappe._dict({'enabled': 0}), hub_connector.username)
+	# 	hub_connector = frappe.get_doc(
+	# 		'Data Migration Connector', 'Hub Connector')
 
-		if response_doc['enabled'] == 0:
-			self.enabled = 0
-			self.save()
+	# 	connection = hub_connector.get_connection()
+	# 	response_doc = connection.update('User', frappe._dict({'enabled': 0}), hub_connector.username)
+
+	# 	if response_doc['enabled'] == 0:
+	# 		self.enabled = 0
+	# 		self.save()
 
 	def create_hub_connector(self, message):
 		if frappe.db.exists('Data Migration Connector', 'Hub Connector'):
 			hub_connector = frappe.get_doc('Data Migration Connector', 'Hub Connector')
+			hub_connector.hostname = self.get_hub_url()
 			hub_connector.username = message['email']
 			hub_connector.password = message['password']
 			hub_connector.save()
@@ -120,7 +78,7 @@
 			'doctype': 'Data Migration Connector',
 			'connector_type': 'Frappe',
 			'connector_name': 'Hub Connector',
-			'hostname': hub_url,
+			'hostname': self.get_hub_url(),
 			'username': message['email'],
 			'password': message['password']
 		}).insert()
@@ -140,6 +98,9 @@
 	frappe.msgprint(_("Successfully unregistered."))
 
 @frappe.whitelist()
-def sync():
-	hub_settings = frappe.get_doc('Hub Settings')
-	hub_settings.sync()
+def register_seller(**kwargs):
+	settings = frappe.get_doc('Hub Settings')
+	settings.update(kwargs)
+	message = settings.register()
+
+	return message.get('email')
diff --git a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json b/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json
index 063c878..9384adb 100644
--- a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json
+++ b/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json
@@ -3,6 +3,7 @@
  "allow_guest_to_view": 0, 
  "allow_import": 0, 
  "allow_rename": 0, 
+ "autoname": "field:item_code", 
  "beta": 0, 
  "creation": "2018-03-18 09:33:50.267762", 
  "custom": 0, 
@@ -14,6 +15,7 @@
  "fields": [
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -41,6 +43,70 @@
    "search_index": 0, 
    "set_only_once": 0, 
    "translatable": 0, 
+   "unique": 1
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "hub_category", 
+   "fieldtype": "Data", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Hub Category", 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "translatable": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "image_list", 
+   "fieldtype": "Long Text", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Image List", 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }
  ], 
@@ -49,12 +115,12 @@
  "hide_toolbar": 0, 
  "idx": 0, 
  "image_view": 0, 
- "in_create": 1, 
+ "in_create": 0, 
  "is_submittable": 0, 
  "issingle": 0, 
  "istable": 0, 
  "max_attachments": 0, 
- "modified": "2018-03-18 09:34:01.757713", 
+ "modified": "2018-08-19 22:24:06.207307", 
  "modified_by": "Administrator", 
  "module": "Hub Node", 
  "name": "Hub Tracked Item", 
@@ -63,7 +129,6 @@
  "permissions": [
   {
    "amend": 0, 
-   "apply_user_permissions": 0, 
    "cancel": 0, 
    "create": 1, 
    "delete": 1, 
@@ -89,5 +154,6 @@
  "sort_field": "modified", 
  "sort_order": "DESC", 
  "track_changes": 1, 
- "track_seen": 0
+ "track_seen": 0, 
+ "track_views": 0
 }
\ No newline at end of file
diff --git a/erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.js b/erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.js
deleted file mode 100644
index 9f7314d..0000000
--- a/erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Hub Tracked Item", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Hub Tracked Item
-		() => frappe.tests.make('Hub Tracked Item', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hub_node/legacy.py b/erpnext/hub_node/legacy.py
new file mode 100644
index 0000000..7218d3f
--- /dev/null
+++ b/erpnext/hub_node/legacy.py
@@ -0,0 +1,143 @@
+from __future__ import unicode_literals
+import frappe, json
+from frappe.utils import nowdate
+from frappe.frappeclient import FrappeClient
+from frappe.utils.nestedset import get_root_of
+from frappe.contacts.doctype.contact.contact import get_default_contact
+
+def get_list(doctype, start, limit, fields, filters, order_by):
+	pass
+
+def get_hub_connection():
+	if frappe.db.exists('Data Migration Connector', 'Hub Connector'):
+		hub_connector = frappe.get_doc('Data Migration Connector', 'Hub Connector')
+		hub_connection = hub_connector.get_connection()
+		return hub_connection.connection
+
+	# read-only connection
+	hub_connection = FrappeClient(frappe.conf.hub_url)
+	return hub_connection
+
+def make_opportunity(buyer_name, email_id):
+	buyer_name = "HUB-" + buyer_name
+
+	if not frappe.db.exists('Lead', {'email_id': email_id}):
+		lead = frappe.new_doc("Lead")
+		lead.lead_name = buyer_name
+		lead.email_id = email_id
+		lead.save(ignore_permissions=True)
+
+	o = frappe.new_doc("Opportunity")
+	o.enquiry_from = "Lead"
+	o.lead = frappe.get_all("Lead", filters={"email_id": email_id}, fields = ["name"])[0]["name"]
+	o.save(ignore_permissions=True)
+
+@frappe.whitelist()
+def make_rfq_and_send_opportunity(item, supplier):
+	supplier = make_supplier(supplier)
+	contact = make_contact(supplier)
+	item = make_item(item)
+	rfq = make_rfq(item, supplier, contact)
+	status = send_opportunity(contact)
+
+	return {
+		'rfq': rfq,
+		'hub_document_created': status
+	}
+
+def make_supplier(supplier):
+	# make supplier if not already exists
+	supplier = frappe._dict(json.loads(supplier))
+
+	if not frappe.db.exists('Supplier', {'supplier_name': supplier.supplier_name}):
+		supplier_doc = frappe.get_doc({
+			'doctype': 'Supplier',
+			'supplier_name': supplier.supplier_name,
+			'supplier_group': supplier.supplier_group,
+			'supplier_email': supplier.supplier_email
+		}).insert()
+	else:
+		supplier_doc = frappe.get_doc('Supplier', supplier.supplier_name)
+
+	return supplier_doc
+
+def make_contact(supplier):
+	contact_name = get_default_contact('Supplier', supplier.supplier_name)
+	# make contact if not already exists
+	if not contact_name:
+		contact = frappe.get_doc({
+			'doctype': 'Contact',
+			'first_name': supplier.supplier_name,
+			'email_id': supplier.supplier_email,
+			'is_primary_contact': 1,
+			'links': [
+				{'link_doctype': 'Supplier', 'link_name': supplier.supplier_name}
+			]
+		}).insert()
+	else:
+		contact = frappe.get_doc('Contact', contact_name)
+
+	return contact
+
+def make_item(item):
+	# make item if not already exists
+	item = frappe._dict(json.loads(item))
+
+	if not frappe.db.exists('Item', {'item_code': item.item_code}):
+		item_doc = frappe.get_doc({
+			'doctype': 'Item',
+			'item_code': item.item_code,
+			'item_group': item.item_group,
+			'is_item_from_hub': 1
+		}).insert()
+	else:
+		item_doc = frappe.get_doc('Item', item.item_code)
+
+	return item_doc
+
+def make_rfq(item, supplier, contact):
+	# make rfq
+	rfq = frappe.get_doc({
+		'doctype': 'Request for Quotation',
+		'transaction_date': nowdate(),
+		'status': 'Draft',
+		'company': frappe.db.get_single_value('Hub Settings', 'company'),
+		'message_for_supplier': 'Please supply the specified items at the best possible rates',
+		'suppliers': [
+			{ 'supplier': supplier.name, 'contact': contact.name }
+		],
+		'items': [
+			{
+				'item_code': item.item_code,
+				'qty': 1,
+				'schedule_date': nowdate(),
+				'warehouse': item.default_warehouse or get_root_of("Warehouse"),
+				'description': item.description,
+				'uom': item.stock_uom
+			}
+		]
+	}).insert()
+
+	rfq.save()
+	rfq.submit()
+	return rfq
+
+def send_opportunity(contact):
+	# Make Hub Message on Hub with lead data
+	doc = {
+		'doctype': 'Lead',
+		'lead_name': frappe.db.get_single_value('Hub Settings', 'company'),
+		'email_id': frappe.db.get_single_value('Hub Settings', 'user')
+	}
+
+	args = frappe._dict(dict(
+		doctype='Hub Message',
+		reference_doctype='Lead',
+		data=json.dumps(doc),
+		user=contact.email_id
+	))
+
+	connection = get_hub_connection()
+	response = connection.insert('Hub Message', args)
+
+	return response.ok
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index cbc02e2..8e02e42 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -557,5 +557,8 @@
 erpnext.patches.v11_0.update_allow_transfer_for_manufacture
 erpnext.patches.v11_0.add_item_group_defaults
 erpnext.patches.v10_0.update_address_template_for_india
+execute:frappe.delete_doc("Page", "hub")
+erpnext.patches.v11_0.reset_publish_in_hub_for_all_items
+erpnext.patches.v11_0.update_hub_url
 erpnext.patches.v10_0.set_discount_amount
 erpnext.patches.v10_0.recalculate_gross_margin_for_project
diff --git a/erpnext/patches/v11_0/reset_publish_in_hub_for_all_items.py b/erpnext/patches/v11_0/reset_publish_in_hub_for_all_items.py
new file mode 100644
index 0000000..fac772c
--- /dev/null
+++ b/erpnext/patches/v11_0/reset_publish_in_hub_for_all_items.py
@@ -0,0 +1,5 @@
+import frappe
+
+def execute():
+	frappe.reload_doc('stock', 'doctype', 'item')
+	frappe.db.sql("""update `tabItem` set publish_in_hub = 0""")
diff --git a/erpnext/patches/v11_0/update_hub_url.py b/erpnext/patches/v11_0/update_hub_url.py
new file mode 100644
index 0000000..ced2aaf
--- /dev/null
+++ b/erpnext/patches/v11_0/update_hub_url.py
@@ -0,0 +1,5 @@
+import frappe
+
+def execute():
+	frappe.reload_doc('hub_node', 'doctype', 'Hub Settings')
+	frappe.db.set_value('Hub Settings', 'Hub Settings', 'hub_url', 'https://hubmarket.org')
diff --git a/erpnext/public/build.json b/erpnext/public/build.json
index 75809ce..e62ae59 100644
--- a/erpnext/public/build.json
+++ b/erpnext/public/build.json
@@ -1,52 +1,55 @@
 {
-	"css/erpnext.css": [
-		"public/less/erpnext.less",
-		"public/less/hub.less"
-	],
-	"js/erpnext-web.min.js": [
-		"public/js/website_utils.js",
-		"public/js/shopping_cart.js"
-	],
+    "css/erpnext.css": [
+        "public/less/erpnext.less",
+        "public/less/hub.less"
+    ],
+    "js/erpnext-web.min.js": [
+        "public/js/website_utils.js",
+        "public/js/shopping_cart.js"
+    ],
 	"css/erpnext-web.css": [
 		"public/less/website.less"
 	],
-	"js/erpnext.min.js": [
-		"public/js/conf.js",
-		"public/js/utils.js",
-		"public/js/queries.js",
-		"public/js/sms_manager.js",
-		"public/js/utils/party.js",
-		"public/js/templates/address_list.html",
-		"public/js/templates/contact_list.html",
-		"public/js/controllers/stock_controller.js",
-		"public/js/payment/payments.js",
-		"public/js/controllers/taxes_and_totals.js",
-		"public/js/controllers/transaction.js",
-		"public/js/pos/pos.html",
-		"public/js/pos/pos_bill_item.html",
-		"public/js/pos/pos_bill_item_new.html",
-		"public/js/pos/pos_selected_item.html",
-		"public/js/pos/pos_item.html",
-		"public/js/pos/pos_tax_row.html",
-		"public/js/pos/customer_toolbar.html",
-		"public/js/pos/pos_invoice_list.html",
-		"public/js/payment/pos_payment.html",
-		"public/js/payment/payment_details.html",
-		"public/js/templates/item_selector.html",
+    "js/marketplace.min.js": [
+        "public/js/hub/marketplace.js"
+    ],
+    "js/erpnext.min.js": [
+        "public/js/conf.js",
+        "public/js/utils.js",
+        "public/js/queries.js",
+        "public/js/sms_manager.js",
+        "public/js/utils/party.js",
+        "public/js/templates/address_list.html",
+        "public/js/templates/contact_list.html",
+        "public/js/controllers/stock_controller.js",
+        "public/js/payment/payments.js",
+        "public/js/controllers/taxes_and_totals.js",
+        "public/js/controllers/transaction.js",
+        "public/js/pos/pos.html",
+        "public/js/pos/pos_bill_item.html",
+        "public/js/pos/pos_bill_item_new.html",
+        "public/js/pos/pos_selected_item.html",
+        "public/js/pos/pos_item.html",
+        "public/js/pos/pos_tax_row.html",
+        "public/js/pos/customer_toolbar.html",
+        "public/js/pos/pos_invoice_list.html",
+        "public/js/payment/pos_payment.html",
+        "public/js/payment/payment_details.html",
+        "public/js/templates/item_selector.html",
 		"public/js/templates/employees_to_mark_attendance.html",
-		"public/js/utils/item_selector.js",
-		"public/js/help_links.js",
-		"public/js/agriculture/ternary_plot.js",
-		"public/js/templates/item_quick_entry.html",
-		"public/js/utils/item_quick_entry.js",
+        "public/js/utils/item_selector.js",
+        "public/js/help_links.js",
+        "public/js/agriculture/ternary_plot.js",
+        "public/js/templates/item_quick_entry.html",
+        "public/js/utils/item_quick_entry.js",
 		"public/js/utils/customer_quick_entry.js",
-		"public/js/education/student_button.html",
-		"public/js/education/assessment_result_tool.html",
-		"public/js/hub/hub_factory.js"
-	],
-	"js/item-dashboard.min.js": [
-		"stock/dashboard/item_dashboard.html",
-		"stock/dashboard/item_dashboard_list.html",
-		"stock/dashboard/item_dashboard.js"
-	]
+        "public/js/education/student_button.html",
+        "public/js/education/assessment_result_tool.html",
+        "public/js/hub/hub_factory.js"
+    ],
+    "js/item-dashboard.min.js": [
+        "stock/dashboard/item_dashboard.html",
+        "stock/dashboard/item_dashboard_list.html",
+        "stock/dashboard/item_dashboard.js"
+    ]
 }
diff --git a/erpnext/public/js/hub/PageContainer.vue b/erpnext/public/js/hub/PageContainer.vue
new file mode 100644
index 0000000..cec4c8d
--- /dev/null
+++ b/erpnext/public/js/hub/PageContainer.vue
@@ -0,0 +1,101 @@
+<template>
+	<div class="hub-page-container">
+		<component :is="current_page"></component>
+	</div>
+</template>
+
+<script>
+
+import Home from './pages/Home.vue';
+import Search from './pages/Search.vue';
+import Category from './pages/Category.vue';
+import SavedItems from './pages/SavedItems.vue';
+import PublishedItems from './pages/PublishedItems.vue';
+import Item from './pages/Item.vue';
+import Seller from './pages/Seller.vue';
+import Publish from './pages/Publish.vue';
+import Buying from './pages/Buying.vue';
+import Selling from './pages/Selling.vue';
+import Messages from './pages/Messages.vue';
+import Profile from './pages/Profile.vue';
+import NotFound from './pages/NotFound.vue';
+
+const route_map = {
+	'marketplace/home': Home,
+	'marketplace/search/:keyword': Search,
+	'marketplace/category/:category': Category,
+	'marketplace/item/:item': Item,
+	'marketplace/seller/:seller': Seller,
+	'marketplace/not-found': NotFound,
+
+	// Registered seller routes
+	'marketplace/profile': Profile,
+	'marketplace/saved-items': SavedItems,
+	'marketplace/publish': Publish,
+	'marketplace/published-items': PublishedItems,
+	'marketplace/buying': Buying,
+	'marketplace/buying/:item': Messages,
+	'marketplace/selling': Selling,
+	'marketplace/selling/:buyer/:item': Messages
+}
+
+export default {
+	data() {
+		return {
+			current_page: this.get_current_page()
+		}
+	},
+	mounted() {
+		frappe.route.on('change', () => {
+			this.set_current_page();
+			frappe.utils.scroll_to(0);
+		});
+	},
+	methods: {
+		set_current_page() {
+			this.current_page = this.get_current_page();
+		},
+		get_current_page() {
+			const curr_route = frappe.get_route_str();
+			let route = Object.keys(route_map).filter(route => route == curr_route)[0];
+			if (!route) {
+				// find route by matching it with dynamic part
+				const curr_route_parts = curr_route.split('/');
+				const weighted_routes = Object.keys(route_map)
+					.map(route_str => route_str.split('/'))
+					.filter(route_parts => route_parts.length === curr_route_parts.length)
+					.reduce((obj, route_parts) => {
+						const key = route_parts.join('/');
+						let weight = 0;
+						route_parts.forEach((part, i) => {
+							const curr_route_part = curr_route_parts[i];
+							if (part === curr_route_part || part.includes(':')) {
+								weight += 1;
+							}
+						});
+
+						obj[key] = weight;
+						return obj;
+					}, {});
+
+				// get the route with the highest weight
+				for (let key in weighted_routes) {
+					const route_weight = weighted_routes[key];
+					if (route_weight === curr_route_parts.length) {
+						route = key;
+						break;
+					} else {
+						route = null;
+					}
+				}
+			}
+
+			if (!route) {
+				return NotFound;
+			}
+
+			return route_map[route];
+		}
+	}
+}
+</script>
diff --git a/erpnext/public/js/hub/Sidebar.vue b/erpnext/public/js/hub/Sidebar.vue
new file mode 100644
index 0000000..cff580e
--- /dev/null
+++ b/erpnext/public/js/hub/Sidebar.vue
@@ -0,0 +1,105 @@
+<template>
+	<div ref="sidebar-container">
+		<ul class="list-unstyled hub-sidebar-group" data-nav-buttons>
+			<li class="hub-sidebar-item" v-for="item in items" :key="item.label" v-route="item.route" v-show="item.condition === undefined || item.condition()">
+				{{ item.label }}
+			</li>
+		</ul>
+		<ul class="list-unstyled hub-sidebar-group" data-categories>
+			<li class="hub-sidebar-item is-title bold text-muted">
+				{{ __('Categories') }}
+			</li>
+			<li class="hub-sidebar-item" v-for="category in categories" :key="category.label" v-route="category.route">
+				{{ category.label }}
+			</li>
+		</ul>
+	</div>
+</template>
+<script>
+export default {
+	data() {
+		return {
+			hub_registered: hub.settings.registered && frappe.session.user === hub.settings.company_email,
+			items: [
+				{
+					label: __('Browse'),
+					route: 'marketplace/home'
+				},
+				{
+					label: __('Saved Items'),
+					route: 'marketplace/saved-items',
+					condition: () => this.hub_registered
+				},
+				{
+					label: __('Your Profile'),
+					route: 'marketplace/profile',
+					condition: () => this.hub_registered
+				},
+				{
+					label: __('Your Items'),
+					route: 'marketplace/published-items',
+					condition: () => this.hub_registered
+				},
+				{
+					label: __('Publish Items'),
+					route: 'marketplace/publish',
+					condition: () => this.hub_registered
+				},
+				{
+					label: __('Selling'),
+					route: 'marketplace/selling',
+					condition: () => this.hub_registered
+				},
+				{
+					label: __('Buying'),
+					route: 'marketplace/buying',
+					condition: () => this.hub_registered
+				},
+			],
+			categories: [],
+		}
+	},
+	created() {
+		this.get_categories()
+			.then(categories => {
+				this.categories = categories.map(c => {
+					return {
+						label: __(c.name),
+						route: 'marketplace/category/' + c.name
+					}
+				});
+				this.categories.unshift({
+					label: __('All'),
+					route: 'marketplace/home'
+				});
+				this.$nextTick(() => {
+					this.update_sidebar_state();
+				});
+			});
+
+		erpnext.hub.on('seller-registered', () => {
+			this.hub_registered = true;
+		})
+	},
+	mounted() {
+		this.update_sidebar_state();
+		frappe.route.on('change', () => this.update_sidebar_state());
+	},
+	methods: {
+		get_categories() {
+			return hub.call('get_categories');
+		},
+		update_sidebar_state() {
+			const container = $(this.$refs['sidebar-container']);
+			const route = frappe.get_route();
+			const route_str = route.join('/');
+			const part_route_str = route.slice(0, 2).join('/');
+			const $sidebar_item = container.find(`[data-route="${route_str}"], [data-route="${part_route_str}"]`);
+
+			const $siblings = container.find('[data-route]');
+			$siblings.removeClass('active').addClass('text-muted');
+			$sidebar_item.addClass('active').removeClass('text-muted');
+		},
+	}
+}
+</script>
diff --git a/erpnext/public/js/hub/components/CommentInput.vue b/erpnext/public/js/hub/components/CommentInput.vue
new file mode 100644
index 0000000..cc430d0
--- /dev/null
+++ b/erpnext/public/js/hub/components/CommentInput.vue
@@ -0,0 +1,38 @@
+<template>
+	<div>
+		<div ref="comment-input"></div>
+		<div class="level">
+			<div class="level-left">
+				<span class="text-muted">{{ __('Ctrl + Enter to submit') }}</span>
+			</div>
+			<div class="level-right">
+				<button class="btn btn-primary btn-xs" @click="submit_input">{{ __('Submit') }}</button>
+			</div>
+		</div>
+	</div>
+</template>
+<script>
+export default {
+	mounted() {
+		this.make_input();
+	},
+	methods: {
+		make_input() {
+			this.message_input = new frappe.ui.CommentArea({
+				parent: this.$refs['comment-input'],
+				on_submit: (message) => {
+					this.message_input.reset();
+					this.$emit('change', message);
+				},
+				no_wrapper: true
+			});
+		},
+		submit_input() {
+			if (!this.message_input) return;
+			const value = this.message_input.val();
+			if (!value) return;
+			this.message_input.submit();
+		}
+	}
+}
+</script>
diff --git a/erpnext/public/js/hub/components/DetailHeaderItem.vue b/erpnext/public/js/hub/components/DetailHeaderItem.vue
new file mode 100644
index 0000000..8ca4379
--- /dev/null
+++ b/erpnext/public/js/hub/components/DetailHeaderItem.vue
@@ -0,0 +1,20 @@
+<template>
+	<p class="text-muted" v-html="header_item"></p>
+</template>
+
+<script>
+
+const spacer = '<span aria-hidden="true"> · </span>';
+
+export default {
+	name: 'detail-header-item',
+	props: ['value'],
+	data() {
+		return {
+			header_item: Array.isArray(this.value)
+				? this.value.join(spacer)
+				: this.value
+		}
+	},
+}
+</script>
diff --git a/erpnext/public/js/hub/components/DetailView.vue b/erpnext/public/js/hub/components/DetailView.vue
new file mode 100644
index 0000000..70ec94c
--- /dev/null
+++ b/erpnext/public/js/hub/components/DetailView.vue
@@ -0,0 +1,86 @@
+<template>
+	<div class="hub-item-container">
+		<div class="row visible-xs">
+			<div class="col-xs-12 margin-bottom">
+				<button class="btn btn-xs btn-default" data-route="marketplace/home">{{ back_to_home_text }}</button>
+			</div>
+		</div>
+
+		<div v-if="show_skeleton" class="row margin-bottom">
+			<div class="col-md-3">
+				<div class="hub-item-skeleton-image"></div>
+			</div>
+			<div class="col-md-6">
+				<h2 class="hub-skeleton" style="width: 75%;">Name</h2>
+				<div class="text-muted">
+					<p class="hub-skeleton" style="width: 35%;">Details</p>
+					<p class="hub-skeleton" style="width: 50%;">Ratings</p>
+				</div>
+				<hr>
+				<div class="hub-item-description">
+					<p class="hub-skeleton">Desc</p>
+					<p class="hub-skeleton" style="width: 85%;">Desc</p>
+				</div>
+			</div>
+		</div>
+
+		<div v-else>
+			<div class="row margin-bottom">
+				<div class="col-md-3">
+					<div class="hub-item-image">
+						<img v-img-src="image">
+					</div>
+				</div>
+				<div class="col-md-8">
+					<h2>{{ title }}</h2>
+					<div class="text-muted">
+						<slot name="detail-header-item"></slot>
+					</div>
+				</div>
+
+				<div v-if="menu_items" class="col-md-1">
+					<div class="dropdown pull-right hub-item-dropdown">
+						<a class="dropdown-toggle btn btn-xs btn-default" data-toggle="dropdown">
+							<span class="caret"></span>
+						</a>
+						<ul class="dropdown-menu dropdown-right" role="menu">
+							<li v-for="menu_item in menu_items"
+								v-if="menu_item.condition"
+								:key="menu_item.label"
+							>
+								<a @click="menu_item.action">{{ menu_item.label }}</a>
+							</li>
+						</ul>
+					</div>
+				</div>
+			</div>
+			<div v-for="section in sections" class="row hub-item-description margin-bottom"
+				:key="section.title"
+			>
+				<h6 class="col-md-12 margin-top">
+					<b class="text-muted">{{ section.title }}</b>
+				</h6>
+				<p class="col-md-12" v-html="section.content">
+				</p>
+			</div>
+		</div>
+
+	</div>
+</template>
+
+<script>
+
+export default {
+	name: 'detail-view',
+	props: ['title', 'image', 'sections', 'show_skeleton', 'menu_items'],
+	data() {
+		return {
+			back_to_home_text: __('Back to Home')
+		}
+	},
+	computed: {}
+}
+</script>
+
+<style lang="less" scoped>
+</style>
diff --git a/erpnext/public/js/hub/components/EmptyState.vue b/erpnext/public/js/hub/components/EmptyState.vue
new file mode 100644
index 0000000..d6216c9
--- /dev/null
+++ b/erpnext/public/js/hub/components/EmptyState.vue
@@ -0,0 +1,50 @@
+<template>
+	<div class="empty-state flex flex-column"
+		:class="{ 'bordered': bordered, 'align-center': centered, 'justify-center': centered }"
+		:style="{ height: height + 'px' }"
+	>
+		<p class="text-muted">{{ message }}</p>
+		<p v-if="action">
+			<button class="btn btn-default btn-xs"
+				@click="action.on_click"
+			>
+				{{ action.label }}
+			</button>
+		</p>
+	</div>
+</template>
+
+<script>
+
+export default {
+	name: 'empty-state',
+	props: {
+		message: String,
+		bordered: Boolean,
+		height: Number,
+		action: Object,
+		centered: {
+			type: Boolean,
+			default: true
+		}
+	}
+}
+</script>
+
+<style lang="less">
+	@import "../../../../../../frappe/frappe/public/less/variables.less";
+
+	.empty-state {
+		height: 500px;
+	}
+
+	.empty-state.bordered {
+		border-radius: 4px;
+		border: 1px solid @border-color;
+		border-style: dashed;
+
+		// bad, due to item card column layout, that is inner 15px margin
+		margin: 0 15px;
+	}
+
+</style>
diff --git a/erpnext/public/js/hub/components/ItemCard.vue b/erpnext/public/js/hub/components/ItemCard.vue
new file mode 100644
index 0000000..f34fddc
--- /dev/null
+++ b/erpnext/public/js/hub/components/ItemCard.vue
@@ -0,0 +1,142 @@
+<template>
+	<div v-if="seen" class="col-md-3 col-sm-4 col-xs-6 hub-card-container">
+		<div class="hub-card"
+			@click="on_click(item_id)"
+		>
+			<div class="hub-card-header flex justify-between">
+				<div class="ellipsis" :style="{ width: '85%' }">
+					<div class="hub-card-title ellipsis bold">{{ title }}</div>
+					<div class="hub-card-subtitle ellipsis text-muted" v-html='subtitle'></div>
+				</div>
+				<i v-if="allow_clear"
+					class="octicon octicon-x text-extra-muted"
+					@click.stop="$emit('remove-item', item_id)"
+				>
+				</i>
+			</div>
+			<div class="hub-card-body">
+				<img class="hub-card-image" v-img-src="item.image"/>
+				<div class="hub-card-overlay">
+					<div v-if="is_local" class="hub-card-overlay-body">
+						<div class="hub-card-overlay-button">
+							<button class="btn btn-default zoom-view">
+								<i class="octicon octicon-pencil text-muted"></i>
+							</button>
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+
+export default {
+	name: 'item-card',
+	props: ['item', 'item_id_fieldname', 'is_local', 'on_click', 'allow_clear', 'seen'],
+	computed: {
+		title() {
+			const item_name = this.item.item_name || this.item.name;
+			return strip_html(item_name);
+		},
+		subtitle() {
+			const dot_spacer = '<span aria-hidden="true"> · </span>';
+			if(this.is_local){
+				return comment_when(this.item.creation);
+			} else {
+				let subtitle_items = [comment_when(this.item.creation)];
+				const rating = this.item.average_rating;
+
+				if (rating > 0) {
+					subtitle_items.push(rating + `<i class='fa fa-fw fa-star-o'></i>`)
+				}
+
+				subtitle_items.push(this.item.company);
+
+				return subtitle_items.join(dot_spacer);
+			}
+		},
+		item_id() {
+			return this.item[this.item_id_fieldname];
+		}
+	}
+}
+</script>
+
+<style lang="less" scoped>
+	@import "../../../../../../frappe/frappe/public/less/variables.less";
+
+	.hub-card {
+		margin-bottom: 25px;
+		position: relative;
+		border: 1px solid @border-color;
+		border-radius: 4px;
+		overflow: hidden;
+		cursor: pointer;
+
+		&:hover .hub-card-overlay {
+			display: block;
+		}
+
+		.octicon-x {
+			display: block;
+			font-size: 20px;
+			margin-left: 10px;
+			cursor: pointer;
+		}
+	}
+
+	.hub-card.closable {
+		.octicon-x {
+			display: block;
+		}
+	}
+
+	.hub-card.is-local {
+		&.active {
+			.hub-card-header {
+				background-color: #f4ffe5;
+			}
+		}
+	}
+
+	.hub-card-header {
+		position: relative;
+		padding: 12px 15px;
+		height: 60px;
+		border-bottom: 1px solid @border-color;
+	}
+
+	.hub-card-body {
+		position: relative;
+		height: 200px;
+	}
+
+	.hub-card-overlay {
+		display: none;
+		position: absolute;
+		top: 0;
+		width: 100%;
+		height: 100%;
+		background-color: rgba(0, 0, 0, 0.05);
+	}
+
+	.hub-card-overlay-body {
+		position: relative;
+		height: 100%;
+	}
+
+	.hub-card-overlay-button {
+		position: absolute;
+		right: 15px;
+		bottom: 15px;
+	}
+
+	.hub-card-image {
+		width: 100%;
+		height: 100%;
+		object-fit: contain;
+	}
+
+</style>
diff --git a/erpnext/public/js/hub/components/ItemCardsContainer.vue b/erpnext/public/js/hub/components/ItemCardsContainer.vue
new file mode 100644
index 0000000..0a20bcd
--- /dev/null
+++ b/erpnext/public/js/hub/components/ItemCardsContainer.vue
@@ -0,0 +1,62 @@
+<template>
+	<div class="item-cards-container">
+		<empty-state
+			v-if="items.length === 0"
+			:message="empty_state_message"
+			:action="empty_state_action"
+			:bordered="true"
+			:height="empty_state_height"
+		/>
+		<item-card
+			v-for="item in items"
+			:key="container_name + '_' +item[item_id_fieldname]"
+			:item="item"
+			:item_id_fieldname="item_id_fieldname"
+			:is_local="is_local"
+			:on_click="on_click"
+			:allow_clear="editable"
+			:seen="item.hasOwnProperty('seen') ? item.seen : true"
+			@remove-item="$emit('remove-item', item[item_id_fieldname])"
+		>
+		</item-card>
+	</div>
+</template>
+
+<script>
+import ItemCard from './ItemCard.vue';
+import EmptyState from './EmptyState.vue';
+
+export default {
+	name: 'item-cards-container',
+	props: {
+		container_name: String,
+		items: Array,
+		item_id_fieldname: String,
+		is_local: Boolean,
+		on_click: Function,
+		editable: Boolean,
+
+		empty_state_message: String,
+		empty_state_action: Object,
+		empty_state_height: Number,
+		empty_state_bordered: Boolean
+	},
+	components: {
+		ItemCard,
+		EmptyState
+	},
+	watch: {
+		items() {
+			// TODO: handling doesn't work
+			frappe.dom.handle_broken_images($(this.$el));
+		}
+	}
+}
+</script>
+
+<style scoped>
+	.item-cards-container {
+		margin: 0 -15px;
+		overflow: overlay;
+	}
+</style>
diff --git a/erpnext/public/js/hub/components/ItemListCard.vue b/erpnext/public/js/hub/components/ItemListCard.vue
new file mode 100644
index 0000000..70cb566
--- /dev/null
+++ b/erpnext/public/js/hub/components/ItemListCard.vue
@@ -0,0 +1,21 @@
+<template>
+	<div class="hub-list-item" :data-route="item.route">
+		<div class="hub-list-left">
+			<img class="hub-list-image" v-img-src="item.image">
+			<div class="hub-list-body ellipsis">
+				<div class="hub-list-title">{{item.item_name}}</div>
+				<div class="hub-list-subtitle ellipsis">
+					<slot name="subtitle"></slot>
+				</div>
+			</div>
+		</div>
+		<div class="hub-list-right" v-if="message">
+			<span class="text-muted" v-html="frappe.datetime.comment_when(message.creation, true)" />
+		</div>
+	</div>
+</template>
+<script>
+export default {
+	props: ['item', 'message']
+}
+</script>
diff --git a/erpnext/public/js/hub/components/NotificationMessage.vue b/erpnext/public/js/hub/components/NotificationMessage.vue
new file mode 100644
index 0000000..c266726
--- /dev/null
+++ b/erpnext/public/js/hub/components/NotificationMessage.vue
@@ -0,0 +1,38 @@
+<template>
+	<div v-if="message" class="subpage-message">
+        <p class="text-muted flex">
+            <span v-html="message"></span>
+            <i class="octicon octicon-x text-extra-muted"
+                @click="$emit('remove-message')"
+            >
+            </i>
+        </p>
+    </div>
+</template>
+
+<script>
+
+export default {
+	name: 'notification-message',
+	props: {
+		message: String,
+    }
+}
+</script>
+
+<style lang="less" scoped>
+    .subpage-message {
+		p {
+			padding: 10px 15px;
+			margin-top: 0px;
+			margin-bottom: 15px;
+			background-color: #f9fbf7;
+			border-radius: 4px;
+			justify-content: space-between;
+		}
+
+		.octicon-x {
+			cursor: pointer;
+		}
+	}
+</style>
diff --git a/erpnext/public/js/hub/components/Rating.vue b/erpnext/public/js/hub/components/Rating.vue
new file mode 100644
index 0000000..33290b8
--- /dev/null
+++ b/erpnext/public/js/hub/components/Rating.vue
@@ -0,0 +1,16 @@
+<template>
+    <span>
+        <i v-for="index in max_rating"
+            :key="index"
+            class="fa fa-fw star-icon"
+            :class="{'fa-star': index <= rating, 'fa-star-o': index > rating}"
+        >
+        </i>
+    </span>
+</template>
+
+<script>
+export default {
+    props: ['rating', 'max_rating']
+}
+</script>
diff --git a/erpnext/public/js/hub/components/ReviewArea.vue b/erpnext/public/js/hub/components/ReviewArea.vue
new file mode 100644
index 0000000..070d0a6
--- /dev/null
+++ b/erpnext/public/js/hub/components/ReviewArea.vue
@@ -0,0 +1,75 @@
+<template>
+	<div>
+		<div ref="review-area" class="timeline-head"></div>
+		<div class="timeline-items">
+			<review-timeline-item v-for="review in reviews"
+				:key="review.user"
+				:username="review.username"
+				:avatar="review.user_image"
+				:comment_when="when(review.modified)"
+				:rating="review.rating"
+				:subject="review.subject"
+				:content="review.content"
+			>
+			</review-timeline-item>
+		</div>
+	</div>
+</template>
+<script>
+import ReviewTimelineItem from '../components/ReviewTimelineItem.vue';
+
+export default {
+	props: ['hub_item_name'],
+	data() {
+		return {
+			reviews: []
+		}
+	},
+	components: {
+		ReviewTimelineItem
+	},
+	created() {
+		this.get_item_reviews();
+	},
+	mounted() {
+		this.make_input();
+	},
+	methods: {
+		when(datetime) {
+			return comment_when(datetime);
+		},
+
+		get_item_reviews() {
+			hub.call('get_item_reviews', { hub_item_name: this.hub_item_name })
+				.then(reviews => {
+					this.reviews = reviews;
+				})
+				.catch(() => {});
+		},
+
+		make_input() {
+			this.review_area = new frappe.ui.ReviewArea({
+				parent: this.$refs['review-area'],
+				mentions: [],
+				on_submit: this.on_submit_review.bind(this)
+			});
+		},
+
+		on_submit_review(values) {
+			values.user = hub.settings.company_email;
+
+			this.review_area.reset();
+
+			hub.call('add_item_review', {
+				hub_item_name: this.hub_item_name,
+				review: JSON.stringify(values)
+			})
+			.then(this.push_review.bind(this));
+		},
+
+		push_review(review){
+			this.reviews.unshift(review);
+		}
+	}
+}
+</script>
diff --git a/erpnext/public/js/hub/components/ReviewTimelineItem.vue b/erpnext/public/js/hub/components/ReviewTimelineItem.vue
new file mode 100644
index 0000000..f0fe001
--- /dev/null
+++ b/erpnext/public/js/hub/components/ReviewTimelineItem.vue
@@ -0,0 +1,54 @@
+<template>
+    <div class="media timeline-item user-content" data-doctype="${''}" data-name="${''}">
+		<span class="pull-left avatar avatar-medium hidden-xs" style="margin-top: 1px">
+			<!-- ${image_html} -->
+		</span>
+		<div class="pull-left media-body">
+			<div class="media-content-wrapper">
+				<div class="action-btns">
+                    <!-- ${edit_html} -->
+                </div>
+
+				<div class="comment-header clearfix">
+					<span class="pull-left avatar avatar-small visible-xs">
+						<!-- ${image_html} -->
+					</span>
+
+					<div class="asset-details">
+						<span class="author-wrap">
+							<i class="octicon octicon-quote hidden-xs fa-fw"></i>
+							<span>
+                                {{ username }}
+                            </span>
+						</span>
+						<a class="text-muted">
+							<span class="text-muted hidden-xs">&ndash;</span>
+							<span class="hidden-xs" v-html="comment_when"></span>
+						</a>
+					</div>
+				</div>
+				<div class="reply timeline-content-show">
+					<div class="timeline-item-content">
+						<p class="text-muted">
+							<rating :rating="rating" :max_rating="5"></rating>
+						</p>
+						<h6 class="bold">{{ subject }}</h6>
+						<p class="text-muted" v-html="content"></p>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+import Rating from '../components/Rating.vue';
+
+export default {
+    props: ['username', 'comment_when', 'avatar', 'rating', 'subject', 'content'],
+    components: {
+        Rating
+    }
+}
+</script>
+
diff --git a/erpnext/public/js/hub/components/SearchInput.vue b/erpnext/public/js/hub/components/SearchInput.vue
new file mode 100644
index 0000000..4b1ce6e
--- /dev/null
+++ b/erpnext/public/js/hub/components/SearchInput.vue
@@ -0,0 +1,26 @@
+<template>
+  <div class="hub-search-container">
+	<input
+		type="text"
+		class="form-control"
+		:placeholder="placeholder"
+		:value="value"
+		@keydown.enter="on_input">
+  </div>
+</template>
+
+<script>
+export default {
+	props: {
+		placeholder: String,
+		value: String,
+		on_search: Function
+	},
+	methods: {
+		on_input(event) {
+			this.$emit('input', event.target.value);
+			this.on_search();
+		}
+	}
+};
+</script>
diff --git a/erpnext/public/js/hub/components/SectionHeader.vue b/erpnext/public/js/hub/components/SectionHeader.vue
new file mode 100644
index 0000000..05a2f83
--- /dev/null
+++ b/erpnext/public/js/hub/components/SectionHeader.vue
@@ -0,0 +1,3 @@
+<template>
+    <div class="hub-items-header level"><slot></slot></div>
+</template>
diff --git a/erpnext/public/js/hub/components/TimelineItem.vue b/erpnext/public/js/hub/components/TimelineItem.vue
new file mode 100644
index 0000000..d13c842
--- /dev/null
+++ b/erpnext/public/js/hub/components/TimelineItem.vue
@@ -0,0 +1,9 @@
+/* Saving this for later */
+<template>
+	<div class="media timeline-item  notification-content">
+		<div class="small">
+			<i class="octicon octicon-bookmark fa-fw"></i>
+			<span title="Administrator"><b>4 weeks ago</b> Published 1 item to Marketplace</span>
+		</div>
+	</div>
+</template>
diff --git a/erpnext/public/js/hub/components/detail_view.js b/erpnext/public/js/hub/components/detail_view.js
new file mode 100644
index 0000000..d80720c
--- /dev/null
+++ b/erpnext/public/js/hub/components/detail_view.js
@@ -0,0 +1,138 @@
+import { get_rating_html } from './reviews';
+
+function get_detail_view_html(item, allow_edit) {
+	const title = item.item_name || item.name;
+	const seller = item.company;
+
+	const who = __('Posted By {0}', [seller]);
+	const when = comment_when(item.creation);
+
+	const city = item.city ? item.city + ', ' : '';
+	const country = item.country ? item.country : '';
+	const where = `${city}${country}`;
+
+	const dot_spacer = '<span aria-hidden="true"> · </span>';
+
+	const description = item.description || '';
+
+	let stats = __('No views yet');
+	if(item.view_count) {
+		const views_message = __(`${item.view_count} Views`);
+
+		const rating_html = get_rating_html(item.average_rating);
+		const rating_count = item.no_of_ratings > 0 ? `${item.no_of_ratings} reviews` : __('No reviews yet');
+
+		stats = `${views_message}${dot_spacer}${rating_html} (${rating_count})`;
+	}
+
+	let favourite_button = ''
+	if (hub.settings.registered) {
+		favourite_button = !item.favourited
+			? `<button class="btn btn-default text-muted favourite-button" data-action="add_to_favourites">
+				${__('Save')} <i class="octicon octicon-heart text-extra-muted"></i>
+			</button>`
+			: `<button class="btn btn-default text-muted favourite-button disabled" data-action="add_to_favourites">
+				${__('Saved')}
+			</button>`;
+	}
+
+	const contact_seller_button = item.hub_seller !== hub.settings.company_email
+		? `<button class="btn btn-primary" data-action="contact_seller">
+			${__('Contact Seller')}
+		</button>`
+		: '';
+
+	let menu_items = '';
+
+	if(allow_edit) {
+		menu_items = `
+			<li><a data-action="edit_details">${__('Edit Details')}</a></li>
+			<li><a data-action="unpublish_item">${__('Unpublish')}</a></li>`;
+	} else {
+		menu_items = `
+			<li><a data-action="report_item">${__('Report this item')}</a></li>
+		`;
+	}
+
+	const html = `
+		<div class="hub-item-container">
+			<div class="row visible-xs">
+				<div class="col-xs-12 margin-bottom">
+					<button class="btn btn-xs btn-default" data-route="marketplace/home">${__('Back to home')}</button>
+				</div>
+			</div>
+			<div class="row detail-page-section margin-bottom">
+				<div class="col-md-3">
+					<div class="hub-item-image">
+						<img src="${item.image}">
+					</div>
+				</div>
+				<div class="col-md-8 flex flex-column">
+					<div class="detail-page-header">
+						<h2>${title}</h2>
+						<div class="text-muted">
+							<p>${where}${dot_spacer}${when}</p>
+							<p>${stats}</p>
+						</div>
+					</div>
+
+					<div class="page-actions detail-page-actions">
+						${favourite_button}
+						${contact_seller_button}
+					</div>
+				</div>
+				<div class="col-md-1">
+					<div class="dropdown pull-right hub-item-dropdown">
+						<a class="dropdown-toggle btn btn-xs btn-default" data-toggle="dropdown">
+							<span class="caret"></span>
+						</a>
+						<ul class="dropdown-menu dropdown-right" role="menu">
+							${menu_items}
+						</ul>
+					</div>
+				</div>
+			</div>
+			<div class="row hub-item-description">
+				<h6 class="col-md-12 margin-top">
+					<b class="text-muted">Item Description</b>
+				</h6>
+				<p class="col-md-12">
+					${description ? description : __('No details')}
+				</p>
+			</div>
+			<div class="row hub-item-seller">
+
+				<h6 class="col-md-12 margin-top margin-bottom">
+					<b class="text-muted">Seller Information</b>
+				</h6>
+				<div class="col-md-1">
+					<img src="https://picsum.photos/200">
+				</div>
+				<div class="col-md-8">
+					<div class="margin-bottom"><a href="#marketplace/seller/${seller}" class="bold">${seller}</a></div>
+				</div>
+			</div>
+			<!-- review area -->
+			<div class="row hub-item-review-container">
+				<div class="col-md-12 form-footer">
+					<div class="form-comments">
+						<div class="timeline">
+							 <div class="timeline-head"></div>
+							<div class="timeline-items"></div>
+						</div>
+					</div>
+					<div class="pull-right scroll-to-top">
+						<a onclick="frappe.utils.scroll_to(0)"><i class="fa fa-chevron-up text-muted"></i></a>
+					</div>
+				</div>
+			</div>
+		</div>
+	`;
+
+	return html;
+}
+
+export {
+	get_detail_view_html,
+	get_profile_html
+}
diff --git a/erpnext/public/js/hub/components/item_card.js b/erpnext/public/js/hub/components/item_card.js
new file mode 100644
index 0000000..41ab33a
--- /dev/null
+++ b/erpnext/public/js/hub/components/item_card.js
@@ -0,0 +1,80 @@
+function get_buying_item_message_card_html(item) {
+	const item_name = item.item_name || item.name;
+	const title = strip_html(item_name);
+
+	const message = item.recent_message
+	const sender = message.sender === frappe.session.user ? 'You' : message.sender
+	const content = strip_html(message.content)
+
+	// route
+	item.route = `marketplace/buying/${item.name}`
+
+	const item_html = `
+		<div class="col-md-7">
+			<div class="hub-list-item" data-route="${item.route}">
+				<div class="hub-list-left">
+					<img class="hub-list-image" src="${item.image}">
+					<div class="hub-list-body ellipsis">
+						<div class="hub-list-title">${item_name}</div>
+						<div class="hub-list-subtitle ellipsis">
+							<span>${sender}: </span>
+							<span>${content}</span>
+						</div>
+					</div>
+				</div>
+				<div class="hub-list-right">
+					<span class="text-muted">${comment_when(message.creation, true)}</span>
+				</div>
+			</div>
+		</div>
+	`;
+
+	return item_html;
+}
+
+function get_selling_item_message_card_html(item) {
+	const item_name = item.item_name || item.name;
+	const title = strip_html(item_name);
+
+	// route
+	if (!item.route) {
+		item.route = `marketplace/item/${item.name}`
+	}
+
+	let received_messages = '';
+	item.received_messages.forEach(message => {
+		const sender = message.sender === frappe.session.user ? 'You' : message.sender
+		const content = strip_html(message.content)
+
+		received_messages += `
+			<div class="received-message">
+				<span class="text-muted">${comment_when(message.creation, true)}</span>
+				<div class="ellipsis">
+					<span class="bold">${sender}: </span>
+					<span>${content}</span>
+				</div>
+			</div>
+		`
+	});
+
+	const item_html = `
+		<div class="selling-item-message-card">
+			<div class="selling-item-detail" data-route="${item.route}">
+				<img class="item-image" src="${item.image}">
+				<h5 class="item-name">${item_name}</h5>
+				<div class="received-message-container">
+					${received_messages}
+				</div>
+			</div>
+		</div>
+	`;
+
+	return item_html;
+}
+
+export {
+	get_item_card_html,
+	get_local_item_card_html,
+	get_buying_item_message_card_html,
+	get_selling_item_message_card_html
+}
diff --git a/erpnext/public/js/hub/components/item_publish_dialog.js b/erpnext/public/js/hub/components/item_publish_dialog.js
new file mode 100644
index 0000000..e49ba53
--- /dev/null
+++ b/erpnext/public/js/hub/components/item_publish_dialog.js
@@ -0,0 +1,42 @@
+function ItemPublishDialog(primary_action, secondary_action) {
+	let dialog = new frappe.ui.Dialog({
+		title: __('Edit Publishing Details'),
+		fields: [
+			{
+				"label": "Item Code",
+				"fieldname": "item_code",
+				"fieldtype": "Data",
+				"read_only": 1
+			},
+			{
+				"label": "Hub Category",
+				"fieldname": "hub_category",
+				"fieldtype": "Autocomplete",
+				"options": [],
+				"reqd": 1
+			},
+			{
+				"label": "Images",
+				"fieldname": "image_list",
+				"fieldtype": "MultiSelect",
+				"options": [],
+				"reqd": 1
+			}
+		],
+		primary_action_label: primary_action.label || __('Set Details'),
+		primary_action: primary_action.fn,
+		secondary_action: secondary_action.fn
+	});
+
+	hub.call('get_categories')
+		.then(categories => {
+			categories = categories.map(d => d.name);
+			dialog.fields_dict.hub_category.set_data(categories);
+		});
+
+	return dialog;
+}
+
+export {
+	ItemPublishDialog
+}
diff --git a/erpnext/public/js/hub/components/profile_dialog.js b/erpnext/public/js/hub/components/profile_dialog.js
new file mode 100644
index 0000000..e76abfa
--- /dev/null
+++ b/erpnext/public/js/hub/components/profile_dialog.js
@@ -0,0 +1,77 @@
+const ProfileDialog = (title = __('Edit Profile'), action={}, initial_values={}) => {
+	const fields = [
+		{
+			fieldtype: 'Link',
+			fieldname: 'company',
+			label: __('Company'),
+			options: 'Company',
+			onchange: () => {
+				const value = dialog.get_value('company');
+
+				if (value) {
+					frappe.db.get_doc('Company', value)
+						.then(company => {
+							dialog.set_values({
+								country: company.country,
+								company_email: company.email,
+								currency: company.default_currency
+							});
+						});
+				}
+			}
+		},
+		{
+			fieldname: 'company_email',
+			label: __('Email'),
+			fieldtype: 'Data'
+		},
+		{
+			fieldname: 'country',
+			label: __('Country'),
+			fieldtype: 'Read Only'
+		},
+		{
+			fieldname: 'currency',
+			label: __('Currency'),
+			fieldtype: 'Read Only'
+		},
+		{
+			fieldtype: 'Text',
+			label: __('About your Company'),
+			fieldname: 'company_description'
+		}
+	];
+
+	let dialog = new frappe.ui.Dialog({
+		title: title,
+		fields: fields,
+		primary_action_label: action.label || __('Update'),
+		primary_action: () => {
+			const form_values = dialog.get_values();
+			let values_filled = true;
+			const mandatory_fields = ['company', 'company_email', 'company_description'];
+			mandatory_fields.forEach(field => {
+				const value = form_values[field];
+				if (!value) {
+					dialog.set_df_property(field, 'reqd', 1);
+					values_filled = false;
+				}
+			});
+			if (!values_filled) return;
+
+			action.on_submit(form_values);
+		}
+	});
+
+	dialog.set_values(initial_values);
+
+	// Post create
+	const default_company = frappe.defaults.get_default('company');
+	dialog.set_value('company', default_company);
+
+	return dialog;
+}
+
+export {
+	ProfileDialog
+}
diff --git a/erpnext/public/js/hub/components/reviews.js b/erpnext/public/js/hub/components/reviews.js
new file mode 100644
index 0000000..e34d680
--- /dev/null
+++ b/erpnext/public/js/hub/components/reviews.js
@@ -0,0 +1,80 @@
+function get_review_html(review) {
+	let username = review.username || review.user || __("Anonymous");
+
+	let image_html = review.user_image
+		? `<div class="avatar-frame" style="background-image: url(${review.user_image})"></div>`
+		: `<div class="standard-image" style="background-color: #fafbfc">${frappe.get_abbr(username)}</div>`
+
+	let edit_html = review.own
+		? `<div class="pull-right hidden-xs close-btn-container">
+			<span class="small text-muted">
+				${'data.delete'}
+			</span>
+		</div>
+		<div class="pull-right edit-btn-container">
+			<span class="small text-muted">
+				${'data.edit'}
+			</span>
+		</div>`
+		: '';
+
+	let rating_html = get_rating_html(review.rating);
+
+	return get_timeline_item(review, image_html, edit_html, rating_html);
+}
+
+function get_timeline_item(data, image_html, edit_html, rating_html) {
+	return `<div class="media timeline-item user-content" data-doctype="${''}" data-name="${''}">
+		<span class="pull-left avatar avatar-medium hidden-xs" style="margin-top: 1px">
+			${image_html}
+		</span>
+		<div class="pull-left media-body">
+			<div class="media-content-wrapper">
+				<div class="action-btns">${edit_html}</div>
+
+				<div class="comment-header clearfix">
+					<span class="pull-left avatar avatar-small visible-xs">
+						${image_html}
+					</span>
+
+					<div class="asset-details">
+						<span class="author-wrap">
+							<i class="octicon octicon-quote hidden-xs fa-fw"></i>
+							<span>${data.username}</span>
+						</span>
+						<a class="text-muted">
+							<span class="text-muted hidden-xs">&ndash;</span>
+							<span class="hidden-xs">${comment_when(data.modified)}</span>
+						</a>
+					</div>
+				</div>
+				<div class="reply timeline-content-show">
+					<div class="timeline-item-content">
+						<p class="text-muted">
+							${rating_html}
+						</p>
+						<h6 class="bold">${data.subject}</h6>
+						<p class="text-muted">
+							${data.content}
+						</p>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>`;
+}
+
+function get_rating_html(rating) {
+	let rating_html = ``;
+	for (var i = 0; i < 5; i++) {
+		let star_class = 'fa-star';
+		if (i >= rating) star_class = 'fa-star-o';
+		rating_html += `<i class='fa fa-fw ${star_class} star-icon' data-index=${i}></i>`;
+	}
+	return rating_html;
+}
+
+export {
+	get_review_html,
+    get_rating_html
+}
diff --git a/erpnext/public/js/hub/event_emitter.js b/erpnext/public/js/hub/event_emitter.js
new file mode 100644
index 0000000..1e72881
--- /dev/null
+++ b/erpnext/public/js/hub/event_emitter.js
@@ -0,0 +1,31 @@
+/**
+ * Simple EventEmitter which uses jQuery's event system
+ */
+class EventEmitter {
+    init() {
+        this.jq = jQuery(this);
+    }
+
+    trigger(evt, data) {
+        !this.jq && this.init();
+        this.jq.trigger(evt, data);
+    }
+
+    once(evt, handler) {
+        !this.jq && this.init();
+        this.jq.one(evt, (e, data) => handler(data));
+    }
+
+    on(evt, handler) {
+        !this.jq && this.init();
+        this.jq.bind(evt, (e, data) => handler(data));
+    }
+
+    off(evt, handler) {
+        !this.jq && this.init();
+        this.jq.unbind(evt, (e, data) => handler(data));
+    }
+}
+
+
+export default EventEmitter;
\ No newline at end of file
diff --git a/erpnext/public/js/hub/hub_call.js b/erpnext/public/js/hub/hub_call.js
new file mode 100644
index 0000000..a8bfa2e
--- /dev/null
+++ b/erpnext/public/js/hub/hub_call.js
@@ -0,0 +1,58 @@
+frappe.provide('hub');
+frappe.provide('erpnext.hub');
+
+erpnext.hub.cache = {};
+hub.call = function call_hub_method(method, args={}, clear_cache_on_event) { // eslint-disable-line
+	return new Promise((resolve, reject) => {
+
+		// cache
+		const key = method + JSON.stringify(args);
+		if (erpnext.hub.cache[key]) {
+			resolve(erpnext.hub.cache[key]);
+		}
+
+		// cache invalidation
+		const clear_cache = () => delete erpnext.hub.cache[key];
+
+		if (!clear_cache_on_event) {
+			invalidate_after_5_mins(clear_cache);
+		} else {
+			erpnext.hub.on(clear_cache_on_event, () => {
+				clear_cache(key);
+			});
+		}
+
+		frappe.call({
+			method: 'erpnext.hub_node.api.call_hub_method',
+			args: {
+				method,
+				params: args
+			}
+		}).then(r => {
+			if (r.message) {
+				const response = r.message;
+				if (response.error) {
+					frappe.throw({
+						title: __('Marketplace Error'),
+						message: response.error
+					});
+				}
+
+				erpnext.hub.cache[key] = response;
+				erpnext.hub.trigger(`response:${key}`, { response });
+				resolve(response);
+			}
+			reject(r);
+
+		}).fail(reject);
+	});
+};
+
+function invalidate_after_5_mins(clear_cache) {
+	// cache invalidation after 5 minutes
+	const timeout = 5 * 60 * 1000;
+
+	setTimeout(() => {
+		clear_cache();
+	}, timeout);
+}
diff --git a/erpnext/public/js/hub/hub_factory.js b/erpnext/public/js/hub/hub_factory.js
index d656605..57f27f9 100644
--- a/erpnext/public/js/hub/hub_factory.js
+++ b/erpnext/public/js/hub/hub_factory.js
@@ -1,80 +1,32 @@
-frappe.provide('erpnext.hub.pages');
+frappe.provide('erpnext.hub');
 
-frappe.views.HubFactory = class HubFactory extends frappe.views.Factory {
-	make(route) {
-		const page_name = frappe.get_route_str();
-		const page = route[1];
+frappe.views.marketplaceFactory = class marketplaceFactory extends frappe.views.Factory {
+	show() {
+		if (frappe.pages.marketplace) {
+			frappe.container.change_to('marketplace');
+			erpnext.hub.marketplace.refresh();
+		} else {
+			this.make('marketplace');
+		}
+	}
 
-		const assets = {
-			'List': [
-				'/assets/erpnext/js/hub/hub_listing.js',
-			],
-			'Form': [
-				'/assets/erpnext/js/hub/hub_form.js'
-			]
-		};
-		frappe.model.with_doc('Hub Settings', 'Hub Settings', () => {
-			this.hub_settings = frappe.get_doc('Hub Settings');
+	make(page_name) {
+		const assets = [
+			'/assets/js/marketplace.min.js'
+		];
 
-			if (!erpnext.hub.pages[page_name]) {
-				if(!frappe.is_online()) {
-					this.render_offline_card();
-					return;
-				}
-				if (!route[2]) {
-					frappe.require(assets['List'], () => {
-						if(page === 'Favourites') {
-							erpnext.hub.pages[page_name] = new erpnext.hub['Favourites']({
-								parent: this.make_page(true, page_name),
-								hub_settings: this.hub_settings
-							});
-						} else {
-							erpnext.hub.pages[page_name] = new erpnext.hub[page+'Listing']({
-								parent: this.make_page(true, page_name),
-								hub_settings: this.hub_settings
-							});
-						}
-					});
-				} else if (!route[3]){
-					frappe.require(assets['Form'], () => {
-						erpnext.hub.pages[page_name] = new erpnext.hub[page+'Page']({
-							unique_id: route[2],
-							doctype: route[2],
-							parent: this.make_page(true, page_name),
-							hub_settings: this.hub_settings
-						});
-					});
-				} else {
-					frappe.require(assets['List'], () => {
-						frappe.route_options = {};
-						frappe.route_options["company_name"] = route[2]
-						erpnext.hub.pages[page_name] = new erpnext.hub['ItemListing']({
-							parent: this.make_page(true, page_name),
-							hub_settings: this.hub_settings
-						});
-					});
-				}
-				window.hub_page = erpnext.hub.pages[page_name];
-			} else {
-				frappe.container.change_to(page_name);
-				window.hub_page = erpnext.hub.pages[page_name];
-			}
+		frappe.require(assets, () => {
+			erpnext.hub.marketplace = new erpnext.hub.Marketplace({
+				parent: this.make_page(true, page_name)
+			});
 		});
 	}
-
-	render_offline_card() {
-		let html = `<div class='page-card' style='margin: 140px auto;'>
-			<div class='page-card-head'>
-				<span class='indicator red'>${'Failed to connect'}</span>
-			</div>
-			<p>${ __("Please check your network connection.") }</p>
-			<div><a href='#Hub/Item' class='btn btn-primary btn-sm'>
-				${ __("Reload") }</a></div>
-		</div>`;
-
-		let page = $('#body_div');
-		page.append(html);
-
-		return;
-	}
 };
+
+$(document).on('toolbar_setup', () => {
+	$('#toolbar-user .navbar-reload').after(`
+		<li>
+			<a href="#marketplace/home">${__('Marketplace')}
+		</li>
+	`);
+});
diff --git a/erpnext/public/js/hub/hub_form.js b/erpnext/public/js/hub/hub_form.js
deleted file mode 100644
index 9287e6d..0000000
--- a/erpnext/public/js/hub/hub_form.js
+++ /dev/null
@@ -1,493 +0,0 @@
-frappe.provide('erpnext.hub');
-
-erpnext.hub.HubDetailsPage = class HubDetailsPage extends frappe.views.BaseList {
-	setup_defaults() {
-		super.setup_defaults();
-		this.method = 'erpnext.hub_node.get_details';
-		const route = frappe.get_route();
-		// this.page_name = route[2];
-	}
-
-	setup_fields() {
-		return this.get_meta()
-			.then(r => {
-				this.meta = r.message.meta || this.meta;
-				this.categories = r.message.categories || [];
-				this.bootstrap_data(r.message);
-
-				this.getFormFields();
-			});
-	}
-
-	bootstrap_data() { }
-
-	get_meta() {
-		return new Promise(resolve =>
-			frappe.call('erpnext.hub_node.get_meta', {doctype: 'Hub ' + this.doctype}, resolve));
-	}
-
-
-	set_breadcrumbs() {
-		frappe.breadcrumbs.add({
-			label: __('Hub'),
-			route: '#Hub/' + this.doctype,
-			type: 'Custom'
-		});
-	}
-
-	setup_side_bar() {
-		this.sidebar = new frappe.ui.Sidebar({
-			wrapper: this.$page.find('.layout-side-section'),
-			css_class: 'hub-form-sidebar'
-		});
-	}
-
-	setup_filter_area() { }
-
-	setup_sort_selector() { }
-
-	// let category = this.quick_view.get_values().hub_category;
-	// return new Promise((resolve, reject) => {
-	// 	frappe.call({
-	// 		method: 'erpnext.hub_node.update_category',
-	// 		args: {
-	// 			hub_item_code: values.hub_item_code,
-	// 			category: category,
-	// 		},
-	// 		callback: (r) => {
-	// 			resolve();
-	// 		},
-	// 		freeze: true
-	// 	}).fail(reject);
-	// });
-
-	get_timeline() {
-		return `<div class="timeline">
-			<div class="timeline-head">
-			</div>
-			<div class="timeline-new-email">
-				<button class="btn btn-default btn-reply-email btn-xs">
-					${__("Reply")}
-				</button>
-			</div>
-			<div class="timeline-items"></div>
-		</div>`;
-	}
-
-	get_footer() {
-		return `<div class="form-footer">
-			<div class="after-save">
-				<div class="form-comments"></div>
-			</div>
-			<div class="pull-right scroll-to-top">
-				<a onclick="frappe.utils.scroll_to(0)"><i class="fa fa-chevron-up text-muted"></i></a>
-			</div>
-		</div>`;
-	}
-
-	get_args() {
-		return {
-			hub_sync_id: this.unique_id,
-			doctype: 'Hub ' + this.doctype
-		};
-	}
-
-	prepare_data(r) {
-		this.data = r.message;
-	}
-
-	update_data(r) {
-		this.data = r.message;
-	}
-
-	render() {
-		const image_html = this.data[this.image_field_name] ?
-			`<img src="${this.data[this.image_field_name]}">
-			<span class="helper"></span>` :
-			`<div class="standard-image">${frappe.get_abbr(this.page_title)}</div>`;
-
-		this.sidebar.remove_item('image');
-		this.sidebar.add_item({
-			name: 'image',
-			label: image_html
-		});
-
-		if(!this.form) {
-			let fields = this.formFields;
-			this.form = new frappe.ui.FieldGroup({
-				parent: this.$result,
-				fields
-			});
-			this.form.make();
-		}
-
-		if(this.data.hub_category) {
-			this.form.fields_dict.set_category.hide();
-		}
-
-		this.form.set_values(this.data);
-		this.$result.show();
-
-		this.$timelineList && this.$timelineList.empty();
-		if(this.data.reviews && this.data.reviews.length) {
-			this.data.reviews.map(review => {
-				this.addReviewToTimeline(review);
-			})
-		}
-
-		this.postRender()
-	}
-
-	postRender() {}
-
-	attachFooter() {
-		let footerHtml = `<div class="form-footer">
-			<div class="form-comments"></div>
-			<div class="pull-right scroll-to-top">
-				<a onclick="frappe.utils.scroll_to(0)"><i class="fa fa-chevron-up text-muted"></i></a>
-			</div>
-		</div>`;
-
-		let parent = $('<div>').appendTo(this.page.main.parent());
-		this.$footer = $(footerHtml).appendTo(parent);
-	}
-
-	attachTimeline() {
-		let timelineHtml = `<div class="timeline">
-			<div class="timeline-head">
-			</div>
-			<div class="timeline-new-email">
-				<button class="btn btn-default btn-reply-email btn-xs">
-					${ __("Reply") }
-				</button>
-			</div>
-			<div class="timeline-items"></div>
-		</div>`;
-
-		let parent = this.$footer.find(".form-comments");
-		this.$timeline = $(timelineHtml).appendTo(parent);
-
-		this.$timelineList = this.$timeline.find(".timeline-items");
-	}
-
-	attachReviewArea() {
-		this.comment_area = new frappe.ui.ReviewArea({
-			parent: this.$footer.find('.timeline-head'),
-			mentions: [],
-			on_submit: (val) => {
-				val.user = frappe.session.user;
-				val.username = frappe.session.user_fullname;
-				frappe.call({
-					method: 'erpnext.hub_node.send_review',
-					args: {
-						hub_item_code: this.data.hub_item_code,
-						review: val
-					},
-					callback: (r) => {
-						this.refresh();
-						this.comment_area.reset();
-					},
-					freeze: true
-				});
-			}
-		});
-	}
-
-	addReviewToTimeline(data) {
-		let username = data.username || data.user || __("Anonymous")
-		let imageHtml = data.user_image
-			? `<div class="avatar-frame" style="background-image: url(${data.user_image})"></div>`
-			: `<div class="standard-image" style="background-color: #fafbfc">${frappe.get_abbr(username)}</div>`
-
-		let editHtml = data.own
-			? `<div class="pull-right hidden-xs close-btn-container">
-				<span class="small text-muted">
-					${'data.delete'}
-				</span>
-			</div>
-			<div class="pull-right edit-btn-container">
-				<span class="small text-muted">
-					${'data.edit'}
-				</span>
-			</div>`
-			: '';
-
-		let ratingHtml = '';
-
-		for(var i = 0; i < 5; i++) {
-			let starIcon = 'fa-star-o'
-			if(i < data.rating) {
-				starIcon = 'fa-star';
-			}
-			ratingHtml += `<i class="fa fa-fw ${starIcon} star-icon" data-idx='${i}'></i>`;
-		}
-
-		$(this.getTimelineItem(data, imageHtml, editHtml, ratingHtml))
-			.appendTo(this.$timelineList);
-	}
-
-	getTimelineItem(data, imageHtml, editHtml, ratingHtml) {
-		return `<div class="media timeline-item user-content" data-doctype="${''}" data-name="${''}">
-			<span class="pull-left avatar avatar-medium hidden-xs" style="margin-top: 1px">
-				${imageHtml}
-			</span>
-
-			<div class="pull-left media-body">
-				<div class="media-content-wrapper">
-					<div class="action-btns">${editHtml}</div>
-
-					<div class="comment-header clearfix small ${'linksActive'}">
-						<span class="pull-left avatar avatar-small visible-xs">
-							${imageHtml}
-						</span>
-
-						<div class="asset-details">
-							<span class="author-wrap">
-								<i class="octicon octicon-quote hidden-xs fa-fw"></i>
-								<span>${data.username}</span>
-							</span>
-								<a href="#Form/${''}" class="text-muted">
-									<span class="text-muted hidden-xs">&ndash;</span>
-									<span class="indicator-right ${'green'}
-										delivery-status-indicator">
-										<span class="hidden-xs">${data.pretty_date}</span>
-									</span>
-								</a>
-
-								<a class="text-muted reply-link pull-right timeline-content-show"
-								title="${__('Reply')}"> ${''} </a>
-							<span class="comment-likes hidden-xs">
-								<i class="octicon octicon-heart like-action text-extra-muted not-liked fa-fw">
-								</i>
-								<span class="likes-count text-muted">10</span>
-							</span>
-						</div>
-					</div>
-					<div class="reply timeline-content-show">
-						<div class="timeline-item-content">
-								<p class="text-muted small">
-									<b>${data.subject}</b>
-								</p>
-
-								<hr>
-
-								<p class="text-muted small">
-									${ratingHtml}
-								</p>
-
-								<hr>
-								<p>
-									${data.content}
-								</p>
-						</div>
-					</div>
-				</div>
-			</div>
-		</div>`;
-	}
-
-	prepareFormFields(fields, fieldnames) {
-		return fields
-		.filter(field => fieldnames.includes(field.fieldname))
-		.map(field => {
-			let {
-				label,
-				fieldname,
-				fieldtype,
-			} = field;
-			let read_only = 1;
-			return {
-				label,
-				fieldname,
-				fieldtype,
-				read_only,
-			};
-		});
-	}
-};
-
-erpnext.hub.ItemPage = class ItemPage extends erpnext.hub.HubDetailsPage {
-	constructor(opts) {
-		super(opts);
-
-		this.show();
-	}
-
-	setup_defaults() {
-		super.setup_defaults();
-		this.doctype = 'Item';
-		this.image_field_name = 'image';
-	}
-
-	setup_page_head() {
-		super.setup_page_head();
-		this.set_primary_action();
-	}
-
-	setup_side_bar() {
-		super.setup_side_bar();
-		this.attachFooter();
-		this.attachTimeline();
-		this.attachReviewArea();
-	}
-
-	set_primary_action() {
-		let item = this.data;
-		this.page.set_primary_action(__('Request a Quote'), () => {
-			this.show_rfq_modal()
-				.then(values => {
-					item.item_code = values.item_code;
-					delete values.item_code;
-
-					const supplier = values;
-					return [item, supplier];
-				})
-				.then(([item, supplier]) => {
-					return this.make_rfq(item, supplier, this.page.btn_primary);
-				})
-				.then(r => {
-					console.log(r);
-					if (r.message && r.message.rfq) {
-						this.page.btn_primary.addClass('disabled').html(`<span><i class='fa fa-check'></i> ${__('Quote Requested')}</span>`);
-					} else {
-						throw r;
-					}
-				})
-				.catch((e) => {
-					console.log(e); //eslint-disable-line
-				});
-		}, 'octicon octicon-plus');
-	}
-
-	prepare_data(r) {
-		super.prepare_data(r);
-		this.page.set_title(this.data["item_name"]);
-	}
-
-	make_rfq(item, supplier, btn) {
-		console.log(supplier);
-		return new Promise((resolve, reject) => {
-			frappe.call({
-				method: 'erpnext.hub_node.make_rfq_and_send_opportunity',
-				args: { item, supplier },
-				callback: resolve,
-				btn,
-			}).fail(reject);
-		});
-	}
-
-	postRender() {
-		this.categoryDialog = new frappe.ui.Dialog({
-			title: __('Suggest Category'),
-			fields: [
-				{
-					label: __('Category'),
-					fieldname: 'category',
-					fieldtype: 'Autocomplete',
-					options: this.categories,
-					reqd: 1
-				}
-			],
-			primary_action_label: __("Send"),
-			primary_action: () => {
-				let values = this.categoryDialog.get_values();
-				frappe.call({
-					method: 'erpnext.hub_node.update_category',
-					args: {
-						hub_item_code: this.data.hub_item_code,
-						category: values.category
-					},
-					callback: () => {
-						this.categoryDialog.hide();
-						this.refresh();
-					},
-					freeze: true
-				}).fail(() => {});
-			}
-		});
-	}
-
-	getFormFields() {
-		let colOneFieldnames = ['item_name', 'item_code', 'description'];
-		let colTwoFieldnames = ['seller', 'company_name', 'country'];
-		let colOneFields = this.prepareFormFields(this.meta.fields, colOneFieldnames);
-		let colTwoFields = this.prepareFormFields(this.meta.fields, colTwoFieldnames);
-
-		let miscFields = [
-			{
-				label: __('Category'),
-				fieldname: 'hub_category',
-				fieldtype: 'Data',
-				read_only: 1
-			},
-
-			{
-				label: __('Suggest Category?'),
-				fieldname: 'set_category',
-				fieldtype: 'Button',
-				click: () => {
-					this.categoryDialog.show();
-				}
-			},
-
-			{
-				fieldname: 'cb1',
-				fieldtype: 'Column Break'
-			}
-		];
-		this.formFields = colOneFields.concat(miscFields, colTwoFields);
-	}
-
-	show_rfq_modal() {
-		let item = this.data;
-		return new Promise(res => {
-			let fields = [
-				{ label: __('Item Code'), fieldtype: 'Data', fieldname: 'item_code', default: item.item_code },
-				{ fieldtype: 'Column Break' },
-				{ label: __('Item Group'), fieldtype: 'Link', fieldname: 'item_group', default: item.item_group },
-				{ label: __('Supplier Details'), fieldtype: 'Section Break' },
-				{ label: __('Supplier Name'), fieldtype: 'Data', fieldname: 'supplier_name', default: item.company_name },
-				{ label: __('Supplier Email'), fieldtype: 'Data', fieldname: 'supplier_email', default: item.seller },
-				{ fieldtype: 'Column Break' },
-				{ label: __('Supplier Group'), fieldname: 'supplier_group',
-					fieldtype: 'Link', options: 'Supplier Group' }
-			];
-			fields = fields.map(f => { f.reqd = 1; return f; });
-
-			const d = new frappe.ui.Dialog({
-				title: __('Request for Quotation'),
-				fields: fields,
-				primary_action_label: __('Send'),
-				primary_action: (values) => {
-					res(values);
-					d.hide();
-				}
-			});
-
-			d.show();
-		});
-	}
-}
-
-erpnext.hub.CompanyPage = class CompanyPage extends erpnext.hub.HubDetailsPage {
-	constructor(opts) {
-		super(opts);
-		this.show();
-	}
-
-	setup_defaults() {
-		super.setup_defaults();
-		this.doctype = 'Company';
-		this.image_field_name = 'company_logo';
-	}
-
-	prepare_data(r) {
-		super.prepare_data(r);
-		this.page.set_title(this.data["company_name"]);
-	}
-
-	getFormFields() {
-		let fieldnames = ['company_name', 'description', 'route', 'country', 'seller', 'site_name'];;
-		this.formFields = this.prepareFormFields(this.meta.fields, fieldnames);
-	}
-}
diff --git a/erpnext/public/js/hub/hub_listing.js b/erpnext/public/js/hub/hub_listing.js
deleted file mode 100644
index 0ff7970..0000000
--- a/erpnext/public/js/hub/hub_listing.js
+++ /dev/null
@@ -1,718 +0,0 @@
-frappe.provide('erpnext.hub');
-
-erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList {
-	setup_defaults() {
-		super.setup_defaults();
-		this.page_title = __('');
-		this.method = 'erpnext.hub_node.get_list';
-
-		this.cache = {};
-
-		const route = frappe.get_route();
-		this.page_name = route[1];
-
-		this.menu_items = this.menu_items.concat(this.get_menu_items());
-
-		this.imageFieldName = 'image';
-
-		this.show_filters = 0;
-	}
-
-	set_title() {
-		const title = this.page_title;
-		let iconHtml = `<img class="hub-icon" src="assets/erpnext/images/hub_logo.svg">`;
-		let titleHtml = `<span class="hub-page-title">${title}</span>`;
-		this.page.set_title(iconHtml + titleHtml, '', false, title);
-	}
-
-	setup_fields() {
-		return this.get_meta()
-			.then(r => {
-				this.meta = r.message.meta || this.meta;
-				frappe.model.sync(this.meta);
-				this.bootstrap_data(r.message);
-
-				this.prepareFormFields();
-			});
-	}
-
-	get_meta() {
-		return new Promise(resolve =>
-			frappe.call('erpnext.hub_node.get_meta', {doctype: this.doctype}, resolve));
-	}
-
-	set_breadcrumbs() { }
-
-	prepareFormFields() { }
-
-	bootstrap_data() { }
-
-	get_menu_items() {
-		const items = [
-			{
-				label: __('Hub Settings'),
-				action: () => frappe.set_route('Form', 'Hub Settings'),
-				standard: true
-			},
-			{
-				label: __('Favourites'),
-				action: () => frappe.set_route('Hub', 'Favourites'),
-				standard: true
-			}
-		];
-
-		return items;
-	}
-
-	setup_side_bar() {
-		this.sidebar = new frappe.ui.Sidebar({
-			wrapper: this.page.wrapper.find('.layout-side-section'),
-			css_class: 'hub-sidebar'
-		});
-	}
-
-	setup_sort_selector() {
-		this.sort_selector = new frappe.ui.SortSelector({
-			parent: this.filter_area.$filter_list_wrapper,
-			doctype: this.doctype,
-			args: this.order_by,
-			onchange: () => this.refresh(true)
-		});
-	}
-
-	setup_view() {
-		if(frappe.route_options){
-			const filters = [];
-			for (let field in frappe.route_options) {
-				var value = frappe.route_options[field];
-				this.page.fields_dict[field].set_value(value);
-			}
-		}
-	}
-
-	get_args() {
-		return {
-			doctype: this.doctype,
-			start: this.start,
-			limit: this.page_length,
-			order_by: this.order_by,
-			// fields: this.fields,
-			filters: this.get_filters_for_args()
-		};
-	}
-
-	update_data(r) {
-		const data = r.message;
-
-		if (this.start === 0) {
-			this.data = data;
-		} else {
-			this.data = this.data.concat(data);
-		}
-
-		this.data_dict = {};
-	}
-
-	freeze(toggle) { }
-
-	render() {
-		this.data_dict = {};
-		this.render_image_view();
-
-		this.setup_quick_view();
-		this.setup_like();
-	}
-
-	render_offline_card() {
-		let html = `<div class='page-card'>
-			<div class='page-card-head'>
-				<span class='indicator red'>
-					{{ _("Payment Cancelled") }}</span>
-			</div>
-			<p>${ __("Your payment is cancelled.") }</p>
-			<div><a href='' class='btn btn-primary btn-sm'>
-				${ __("Continue") }</a></div>
-		</div>`;
-
-		let page = this.page.wrapper.find('.layout-side-section')
-		page.append(html);
-
-		return;
-	}
-
-	render_image_view() {
-		var html = this.data.map(this.item_html.bind(this)).join("");
-
-		if (this.start === 0) {
-			// ${this.getHeaderHtml()}
-			this.$result.html(`
-				<div class="image-view-container small">
-					${html}
-				</div>
-			`);
-		}
-
-		if(this.data.length) {
-			this.doc = this.data[0];
-		}
-
-		this.data.map(this.loadImage.bind(this));
-
-		this.data_dict = {};
-		this.data.map(d => {
-			this.data_dict[d.hub_item_code] = d;
-		});
-	}
-
-	getHeaderHtml(title, image, content) {
-		// let company_html =
-		return `
-			<header class="list-row-head text-muted small">
-				<div style="display: flex;">
-					<div class="list-header-icon">
-						<img title="${title}" alt="${title}" src="${image}">
-					</div>
-					<div class="list-header-info">
-						<h5>
-							${title}
-						</h5>
-						<span class="margin-vertical-10 level-item">
-							${content}
-						</span>
-					</div>
-				</div>
-			</header>
-		`;
-	}
-
-	renderHeader() {
-		return `<header class="level list-row-head text-muted small">
-			<div class="level-left list-header-subject">
-				<div class="list-row-col list-subject level ">
-					<img title="Riadco%20Group" alt="Riadco Group" src="https://cdn.pbrd.co/images/HdaPxcg.png">
-					<span class="level-item">Products by Blah blah</span>
-				</div>
-			</div>
-			<div class="level-left checkbox-actions">
-				<div class="level list-subject">
-					<input class="level-item list-check-all hidden-xs" type="checkbox" title="${__("Select All")}">
-					<span class="level-item list-header-meta"></span>
-				</div>
-			</div>
-			<div class="level-right">
-				${''}
-			</div>
-		</header>`;
-	}
-
-	get_image_html(encoded_name, src, alt_text) {
-		return `<img data-name="${encoded_name}" src="${ src }" alt="${ alt_text }">`;
-	}
-
-	get_image_placeholder(title) {
-		return `<span class="placeholder-text">${ frappe.get_abbr(title) }</span>`;
-	}
-
-	loadImage(item) {
-		item._name = encodeURI(item.name);
-		const encoded_name = item._name;
-		const title = strip_html(item[this.meta.title_field || 'name']);
-
-		let placeholder = this.get_image_placeholder(title);
-		let $container = this.$result.find(`.image-field[data-name="${encoded_name}"]`);
-
-		if(!item[this.imageFieldName]) {
-			$container.prepend(placeholder);
-			$container.addClass('no-image');
-		}
-
-		frappe.load_image(item[this.imageFieldName],
-			(imageObj) => {
-				$container.prepend(imageObj)
-			},
-			() => {
-				$container.prepend(placeholder);
-				$container.addClass('no-image');
-			},
-			(imageObj) => {
-				imageObj.title = encoded_name;
-				imageObj.alt = title;
-			}
-		)
-	}
-
-	setup_quick_view() {
-		if(this.quick_view) return;
-
-		this.quick_view = new frappe.ui.Dialog({
-			title: 'Quick View',
-			fields: this.formFields
-		});
-		this.quick_view.set_primary_action(__('Request a Quote'), () => {
-			this.show_rfq_modal()
-				.then(values => {
-					item.item_code = values.item_code;
-					delete values.item_code;
-
-					const supplier = values;
-					return [item, supplier];
-				})
-				.then(([item, supplier]) => {
-					return this.make_rfq(item, supplier, this.page.btn_primary);
-				})
-				.then(r => {
-					console.log(r);
-					if (r.message && r.message.rfq) {
-						this.page.btn_primary.addClass('disabled').html(`<span><i class='fa fa-check'></i> ${__('Quote Requested')}</span>`);
-					} else {
-						throw r;
-					}
-				})
-				.catch((e) => {
-					console.log(e); //eslint-disable-line
-				});
-		}, 'octicon octicon-plus');
-
-		this.$result.on('click', '.btn.zoom-view', (e) => {
-			e.preventDefault();
-			e.stopPropagation();
-			var name = $(e.target).attr('data-name');
-			name = decodeURIComponent(name);
-
-			this.quick_view.set_title(name);
-			let values = this.data_dict[name];
-			this.quick_view.set_values(values);
-
-			let fields = [];
-
-			this.quick_view.show();
-
-			return false;
-		});
-	}
-
-	setup_like() {
-		if(this.setup_like_done) return;
-		this.setup_like_done = 1;
-		this.$result.on('click', '.btn.like-button', (e) => {
-			if($(e.target).hasClass('changing')) return;
-			$(e.target).addClass('changing');
-
-			e.preventDefault();
-			e.stopPropagation();
-
-			var name = $(e.target).attr('data-name');
-			name = decodeURIComponent(name);
-			let values = this.data_dict[name];
-
-			let heart = $(e.target);
-			if(heart.hasClass('like-button')) {
-				heart = $(e.target).find('.octicon');
-			}
-
-			let remove = 1;
-
-			if(heart.hasClass('liked')) {
-				// unlike
-				heart.removeClass('liked');
-			} else {
-				// like
-				remove = 0;
-				heart.addClass('liked');
-			}
-
-			frappe.call({
-				method: 'erpnext.hub_node.update_wishlist_item',
-				args: {
-					item_name: values.hub_item_code,
-					remove: remove
-				},
-				callback: (r) => {
-					let message = __("Added to Favourites");
-					if(remove) {
-						message = __("Removed from Favourites");
-					}
-					frappe.show_alert(message);
-				},
-				freeze: true
-			});
-
-			$(e.target).removeClass('changing');
-			return false;
-		});
-	}
-}
-
-erpnext.hub.ItemListing = class ItemListing extends erpnext.hub.HubListing {
-	constructor(opts) {
-		super(opts);
-		this.show();
-	}
-
-	setup_defaults() {
-		super.setup_defaults();
-		this.doctype = 'Hub Item';
-		this.page_title = __('Marketplace');
-		this.fields = ['name', 'hub_item_code', 'image', 'item_name', 'item_code', 'company_name', 'description', 'country'];
-		this.filters = [];
-	}
-
-	render() {
-		this.data_dict = {};
-		this.render_image_view();
-
-		this.setup_quick_view();
-		this.setup_like();
-	}
-
-	bootstrap_data(response) {
-		let companies = response.companies.map(d => d.name);
-		this.custom_filter_configs = [
-			{
-				fieldtype: 'Autocomplete',
-				label: __('Select Company'),
-				condition: 'like',
-				fieldname: 'company_name',
-				options: companies
-			},
-			{
-				fieldtype: 'Link',
-				label: __('Select Country'),
-				options: 'Country',
-				condition: 'like',
-				fieldname: 'country'
-			}
-		];
-	}
-
-	prepareFormFields() {
-		let fieldnames = ['item_name', 'description', 'company_name', 'country'];
-		this.formFields = this.meta.fields
-			.filter(field => fieldnames.includes(field.fieldname))
-			.map(field => {
-				let {
-					label,
-					fieldname,
-					fieldtype,
-				} = field;
-				let read_only = 1;
-				return {
-					label,
-					fieldname,
-					fieldtype,
-					read_only,
-				};
-			});
-
-		this.formFields.unshift({
-			label: 'image',
-			fieldname: 'image',
-			fieldtype: 'Attach Image'
-		});
-	}
-
-	setup_side_bar() {
-		super.setup_side_bar();
-
-		let $pitch = $(`<div class="border" style="
-				margin-top: 10px;
-				padding: 0px 10px;
-				border-radius: 3px;
-			">
-			<h5>Sell on HubMarket</h5>
-			<p>Over 2000 products listed. Register your company to start selling.</p>
-		</div>`);
-
-		this.sidebar.$sidebar.append($pitch);
-
-		this.category_tree = new frappe.ui.Tree({
-			parent: this.sidebar.$sidebar,
-			label: 'All Categories',
-			expandable: true,
-
-			args: {parent: this.current_category},
-			method: 'erpnext.hub_node.get_categories',
-			on_click: (node) => {
-				this.update_category(node.label);
-			}
-		});
-
-		this.sidebar.add_item({
-			label: __('Companies'),
-			on_click: () => frappe.set_route('Hub', 'Company')
-		}, undefined, true);
-
-		this.sidebar.add_item({
-			label: this.hub_settings.company,
-			on_click: () => frappe.set_route('Form', 'Company', this.hub_settings.company)
-		}, __("Account"));
-
-		this.sidebar.add_item({
-			label: __("Favourites"),
-			on_click: () => frappe.set_route('Hub', 'Favourites')
-		}, __("Account"));
-
-		this.sidebar.add_item({
-			label: __("Settings"),
-			on_click: () => frappe.set_route('Form', 'Hub Settings')
-		}, __("Account"));
-	}
-
-	update_category(label) {
-		this.current_category = (label=='All Categories') ? undefined : label;
-		this.refresh();
-	}
-
-	get_filters_for_args() {
-		if(!this.filter_area) return;
-		let filters = {};
-		this.filter_area.get().forEach(f => {
-			let field = f[1] !== 'name' ? f[1] : 'item_name';
-			filters[field] = [f[2], f[3]];
-		});
-		if(this.current_category) {
-			filters['hub_category'] = this.current_category;
-		}
-		return filters;
-	}
-
-	update_data(r) {
-		super.update_data(r);
-
-		this.data_dict = {};
-		this.data.map(d => {
-			this.data_dict[d.hub_item_code] = d;
-		});
-	}
-
-	item_html(item) {
-		item._name = encodeURI(item.name);
-		const encoded_name = item._name;
-		const title = strip_html(item[this.meta.title_field || 'name']);
-		const _class = !item[this.imageFieldName] ? 'no-image' : '';
-		const route = `#Hub/Item/${item.hub_item_code}`;
-		const company_name = item['company_name'];
-
-		const reviewLength = (item.reviews || []).length;
-		const ratingAverage = reviewLength
-			? item.reviews
-				.map(r => r.rating)
-				.reduce((a, b) => a + b, 0)/reviewLength
-			: -1;
-
-		let ratingHtml = ``;
-
-		for(var i = 0; i < 5; i++) {
-			let starClass = 'fa-star';
-			if(i >= ratingAverage) starClass = 'fa-star-o';
-			ratingHtml += `<i class='fa fa-fw ${starClass} star-icon' data-index=${i}></i>`;
-		}
-
-		let item_html = `
-			<div class="image-view-item">
-				<div class="image-view-header">
-					<div class="list-row-col list-subject ellipsis level">
-						<span class="level-item bold ellipsis" title="McGuffin">
-							<a href="${route}">${title}</a>
-						</span>
-					</div>
-					<div class="text-muted small" style="margin: 5px 0px;">
-						${ratingHtml}
-						(${reviewLength})
-					</div>
-					<div class="list-row-col">
-						<a href="${'#Hub/Company/'+company_name+'/Items'}"><p>${ company_name }</p></a>
-					</div>
-				</div>
-				<div class="image-view-body">
-					<a  data-name="${encoded_name}"
-						title="${encoded_name}"
-						href="${route}"
-					>
-						<div class="image-field ${_class}"
-							data-name="${encoded_name}"
-						>
-							<button class="btn btn-default zoom-view" data-name="${encoded_name}">
-								<i class="octicon octicon-eye" data-name="${encoded_name}"></i>
-							</button>
-							<button class="btn btn-default like-button" data-name="${encoded_name}">
-								<i class="octicon octicon-heart" data-name="${encoded_name}"></i>
-							</button>
-						</div>
-					</a>
-				</div>
-
-			</div>
-		`;
-
-		return item_html;
-	}
-
-};
-
-erpnext.hub.Favourites = class Favourites extends erpnext.hub.ItemListing {
-	constructor(opts) {
-		super(opts);
-		this.show();
-	}
-
-	setup_defaults() {
-		super.setup_defaults();
-		this.doctype = 'Hub Item';
-		this.page_title = __('Favourites');
-		this.fields = ['name', 'hub_item_code', 'image', 'item_name', 'item_code', 'company_name', 'description', 'country'];
-		this.filters = [];
-		this.method = 'erpnext.hub_node.get_item_favourites';
-	}
-
-	setup_filter_area() { }
-
-	setup_sort_selector() { }
-
-	// setupHe
-
-	getHeaderHtml() {
-		return '';
-	}
-
-	get_args() {
-		return {
-			start: this.start,
-			limit: this.page_length,
-			order_by: this.order_by,
-			fields: this.fields
-		};
-	}
-
-	bootstrap_data(response) { }
-
-	prepareFormFields() { }
-
-	setup_side_bar() {
-		this.sidebar = new frappe.ui.Sidebar({
-			wrapper: this.page.wrapper.find('.layout-side-section'),
-			css_class: 'hub-sidebar'
-		});
-
-		this.sidebar.add_item({
-			label: __('Back to Products'),
-			on_click: () => frappe.set_route('Hub', 'Item')
-		});
-	}
-
-	update_category(label) {
-		this.current_category = (label=='All Categories') ? undefined : label;
-		this.refresh();
-	}
-
-	get_filters_for_args() {
-		if(!this.filter_area) return;
-		let filters = {};
-		this.filter_area.get().forEach(f => {
-			let field = f[1] !== 'name' ? f[1] : 'item_name';
-			filters[field] = [f[2], f[3]];
-		});
-		if(this.current_category) {
-			filters['hub_category'] = this.current_category;
-		}
-		return filters;
-	}
-
-	update_data(r) {
-		super.update_data(r);
-
-		this.data_dict = {};
-		this.data.map(d => {
-			this.data_dict[d.hub_item_code] = d;
-		});
-	}
-};
-
-erpnext.hub.CompanyListing = class CompanyListing extends erpnext.hub.HubListing {
-	constructor(opts) {
-		super(opts);
-		this.show();
-	}
-
-	render() {
-		this.data_dict = {};
-		this.render_image_view();
-	}
-
-	setup_defaults() {
-		super.setup_defaults();
-		this.doctype = 'Hub Company';
-		this.page_title = __('Companies');
-		this.fields = ['company_logo', 'name', 'site_name', 'seller_city', 'seller_description', 'seller', 'country', 'company_name'];
-		this.filters = [];
-		this.custom_filter_configs = [
-			{
-				fieldtype: 'Link',
-				label: 'Country',
-				options: 'Country',
-				condition: 'like',
-				fieldname: 'country'
-			}
-		];
-		this.imageFieldName = 'company_logo';
-	}
-
-	setup_side_bar() {
-		this.sidebar = new frappe.ui.Sidebar({
-			wrapper: this.page.wrapper.find('.layout-side-section'),
-			css_class: 'hub-sidebar'
-		});
-
-		this.sidebar.add_item({
-			label: __('Back to Products'),
-			on_click: () => frappe.set_route('Hub', 'Item')
-		});
-	}
-
-	get_filters_for_args() {
-		let filters = {};
-		this.filter_area.get().forEach(f => {
-			let field = f[1] !== 'name' ? f[1] : 'company_name';
-			filters[field] = [f[2], f[3]];
-		});
-		return filters;
-	}
-
-	item_html(company) {
-		company._name = encodeURI(company.company_name);
-		const encoded_name = company._name;
-		const title = strip_html(company.company_name);
-		const _class = !company[this.imageFieldName] ? 'no-image' : '';
-		const company_name = company['company_name'];
-		const route = `#Hub/Company/${company_name}`;
-
-		let image_html = company.company_logo ?
-			`<img src="${company.company_logo}"><span class="helper"></span>` :
-			`<div class="standard-image">${frappe.get_abbr(company.company_name)}</div>`;
-
-		let item_html = `
-			<div class="image-view-item">
-				<div class="image-view-header">
-					<div class="list-row-col list-subject ellipsis level">
-						<span class="level-item bold ellipsis" title="McGuffin">
-							<a href="${route}">${title}</a>
-						</span>
-					</div>
-				</div>
-				<div class="image-view-body">
-					<a  data-name="${encoded_name}"
-						title="${encoded_name}"
-						href="${route}">
-						<div class="image-field ${_class}"
-							data-name="${encoded_name}">
-						</div>
-					</a>
-				</div>
-
-			</div>
-		`;
-
-		return item_html;
-	}
-
-};
\ No newline at end of file
diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js
new file mode 100644
index 0000000..cdf3d23
--- /dev/null
+++ b/erpnext/public/js/hub/marketplace.js
@@ -0,0 +1,115 @@
+import Vue from 'vue/dist/vue.js';
+import './vue-plugins';
+
+// components
+import PageContainer from './PageContainer.vue';
+import Sidebar from './Sidebar.vue';
+import { ProfileDialog } from './components/profile_dialog';
+
+// helpers
+import './hub_call';
+import EventEmitter from './event_emitter';
+
+frappe.provide('hub');
+frappe.provide('erpnext.hub');
+
+$.extend(erpnext.hub, EventEmitter.prototype);
+
+erpnext.hub.Marketplace = class Marketplace {
+	constructor({ parent }) {
+		this.$parent = $(parent);
+		this.page = parent.page;
+
+		frappe.db.get_doc('Hub Settings')
+		.then(doc => {
+				hub.settings = doc;
+				const is_registered = hub.settings.registered;
+				const is_registered_seller = hub.settings.company_email === frappe.session.user;
+				this.setup_header();
+				this.make_sidebar();
+				this.make_body();
+				this.setup_events();
+				this.refresh();
+				if (!is_registered && !is_registered_seller && frappe.user_roles.includes('System Manager')) {
+					this.page.set_primary_action('Become a Seller', this.show_register_dialog.bind(this))
+				}
+			});
+	}
+
+	setup_header() {
+		this.page.set_title(__('Marketplace'));
+	}
+
+	setup_events() {
+		this.$parent.on('click', '[data-route]', (e) => {
+			const $target = $(e.currentTarget);
+			const route = $target.data().route;
+			frappe.set_route(route);
+		});
+
+		// generic action handler
+		this.$parent.on('click', '[data-action]', e => {
+			const $target = $(e.currentTarget);
+			const action = $target.data().action;
+
+			if (action && this[action]) {
+				this[action].apply(this, $target);
+			}
+		})
+	}
+
+	make_sidebar() {
+		this.$sidebar = this.$parent.find('.layout-side-section').addClass('hidden-xs');
+
+		new Vue({
+			el: $('<div>').appendTo(this.$sidebar)[0],
+			render: h => h(Sidebar)
+		});
+	}
+
+	make_body() {
+		this.$body = this.$parent.find('.layout-main-section');
+		this.$page_container = $('<div class="hub-page-container">').appendTo(this.$body);
+
+		new Vue({
+			el: '.hub-page-container',
+			render: h => h(PageContainer)
+		});
+
+		erpnext.hub.on('seller-registered', () => {
+			this.page.clear_primary_action()
+			frappe.db.get_doc('Hub Settings').then((doc)=> {
+				hub.settings = doc;
+			});
+		});
+	}
+
+	refresh() {
+
+	}
+
+	show_register_dialog() {
+		this.register_dialog = ProfileDialog(
+			__('Become a Seller'),
+			{
+				label: __('Register'),
+				on_submit: this.register_seller.bind(this)
+			}
+		);
+
+		this.register_dialog.show();
+	}
+
+	register_seller(form_values) {
+		frappe.call({
+		    method: 'erpnext.hub_node.doctype.hub_settings.hub_settings.register_seller',
+		    args: form_values
+		}).then(() => {
+			this.register_dialog.hide();
+			frappe.set_route('marketplace', 'publish');
+
+		    erpnext.hub.trigger('seller-registered');
+		});
+	}
+
+}
diff --git a/erpnext/public/js/hub/pages/Buying.vue b/erpnext/public/js/hub/pages/Buying.vue
new file mode 100644
index 0000000..ddb4b11
--- /dev/null
+++ b/erpnext/public/js/hub/pages/Buying.vue
@@ -0,0 +1,53 @@
+<template>
+	<div>
+		<section-header>
+			<h4>{{ __('Buying') }}</h4>
+		</section-header>
+		<div class="row" v-if="items && items.length">
+			<div class="col-md-7 margin-bottom"
+				v-for="item of items"
+				:key="item.name"
+			>
+				<item-list-card
+					:item="item"
+					v-route="'marketplace/buying/' + item.name"
+				>
+					<div slot="subtitle">
+						<span>{{item.recent_message.sender}}: </span>
+						<span>{{item.recent_message.content | striphtml}}</span>
+					</div>
+				</item-list-card>
+			</div>
+		</div>
+		<empty-state v-else :message="__('This page keeps track of items you want to buy from sellers.')" :centered="false" />
+	</div>
+</template>
+<script>
+import EmptyState from '../components/EmptyState.vue';
+import SectionHeader from '../components/SectionHeader.vue';
+import ItemListCard from '../components/ItemListCard.vue';
+
+export default {
+	components: {
+		SectionHeader,
+		ItemListCard,
+		EmptyState
+	},
+	data() {
+		return {
+			items: null
+		}
+	},
+	created() {
+		this.get_items_for_messages()
+			.then(items => {
+				this.items = items;
+			});
+	},
+	methods: {
+		get_items_for_messages() {
+			return hub.call('get_buying_items_for_messages', {}, 'action:send_message');
+		}
+	}
+}
+</script>
diff --git a/erpnext/public/js/hub/pages/Category.vue b/erpnext/public/js/hub/pages/Category.vue
new file mode 100644
index 0000000..3a0e6bf
--- /dev/null
+++ b/erpnext/public/js/hub/pages/Category.vue
@@ -0,0 +1,59 @@
+<template>
+	<div
+		class="marketplace-page"
+		:data-page-name="page_name"
+	>
+		<h5>{{ page_title }}</h5>
+
+		<item-cards-container
+			:container_name="page_title"
+			:items="items"
+			:item_id_fieldname="item_id_fieldname"
+			:on_click="go_to_item_details_page"
+			:empty_state_message="empty_state_message"
+		>
+		</item-cards-container>
+	</div>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			page_name: frappe.get_route()[1],
+			category: frappe.get_route()[2],
+			items: [],
+			item_id_fieldname: 'name',
+
+			// Constants
+			empty_state_message: __(`No items in this category yet.`)
+		};
+	},
+	computed: {
+		page_title() {
+			return __(this.category);
+		}
+	},
+	created() {
+		this.get_items();
+	},
+	methods: {
+		get_items() {
+			hub.call('get_items', {
+				filters: {
+					hub_category: this.category
+				}
+			})
+			.then((items) => {
+				this.items = items;
+			})
+		},
+
+		go_to_item_details_page(hub_item_name) {
+			frappe.set_route(`marketplace/item/${hub_item_name}`);
+		}
+	}
+}
+</script>
+
+<style scoped></style>
diff --git a/erpnext/public/js/hub/pages/Home.vue b/erpnext/public/js/hub/pages/Home.vue
new file mode 100644
index 0000000..4f9796d
--- /dev/null
+++ b/erpnext/public/js/hub/pages/Home.vue
@@ -0,0 +1,93 @@
+<template>
+	<div
+		class="marketplace-page"
+		:data-page-name="page_name"
+	>
+		<search-input
+			:placeholder="search_placeholder"
+			:on_search="set_search_route"
+			v-model="search_value"
+		/>
+
+		<div v-for="section in sections" :key="section.title">
+
+			<section-header>
+				<h4>{{ section.title }}</h4>
+				<p v-if="section.expandable" :data-route="'marketplace/category/' + section.title">{{ 'See All' }}</p>
+			</section-header>
+
+			<item-cards-container
+				:container_name="section.title"
+				:items="section.items"
+				:item_id_fieldname="item_id_fieldname"
+				:on_click="go_to_item_details_page"
+			/>
+		</div>
+	</div>
+</template>
+
+<script>
+export default {
+	name: 'home-page',
+	data() {
+		return {
+			page_name: frappe.get_route()[1],
+			item_id_fieldname: 'name',
+			search_value: '',
+
+			sections: [],
+
+			// Constants
+			search_placeholder: __('Search for anything ...'),
+		};
+	},
+	created() {
+		// refreshed
+		this.search_value = '';
+		this.get_items();
+	},
+	methods: {
+		get_items() {
+			hub.call('get_data_for_homepage', {
+				country: frappe.defaults.get_user_default('country')
+			})
+			.then((data) => {
+				this.sections.push({
+					title: __('Explore'),
+					items: data.random_items
+				});
+				if (data.items_by_country.length) {
+					this.sections.push({
+						title: __('Near you'),
+						items: data.items_by_country
+					});
+				}
+
+				const category_items = data.category_items;
+
+				if (category_items) {
+					Object.keys(category_items).map(category => {
+						const items = category_items[category];
+
+						this.sections.push({
+							title: __(category),
+							expandable: true,
+							items
+						});
+					});
+				}
+			})
+		},
+
+		go_to_item_details_page(hub_item_name) {
+			frappe.set_route(`marketplace/item/${hub_item_name}`);
+		},
+
+		set_search_route() {
+			frappe.set_route('marketplace', 'search', this.search_value);
+		},
+	}
+}
+</script>
+
+<style scoped></style>
diff --git a/erpnext/public/js/hub/pages/Item.vue b/erpnext/public/js/hub/pages/Item.vue
new file mode 100644
index 0000000..3305b1d
--- /dev/null
+++ b/erpnext/public/js/hub/pages/Item.vue
@@ -0,0 +1,280 @@
+<template>
+	<div
+		class="marketplace-page"
+		:data-page-name="page_name"
+		v-if="init || item"
+	>
+
+		<detail-view
+			:title="title"
+			:image="image"
+			:sections="sections"
+			:menu_items="menu_items"
+			:show_skeleton="init"
+		>
+			<detail-header-item slot="detail-header-item"
+				:value="item_subtitle"
+			></detail-header-item>
+			<detail-header-item slot="detail-header-item"
+				:value="item_views_and_ratings"
+			></detail-header-item>
+
+			<button slot="detail-header-item"
+				class="btn btn-primary btn-sm margin-top"
+				@click="primary_action.action"
+			>
+				{{ primary_action.label }}
+			</button>
+
+		</detail-view>
+
+		<review-area :hub_item_name="hub_item_name"></review-area>
+	</div>
+</template>
+
+<script>
+import ReviewArea from '../components/ReviewArea.vue';
+import { get_rating_html } from '../components/reviews';
+
+export default {
+	name: 'item-page',
+	components: {
+		ReviewArea
+	},
+	data() {
+		return {
+			page_name: frappe.get_route()[1],
+			hub_item_name: frappe.get_route()[2],
+
+			init: true,
+
+			item: null,
+			title: null,
+			image: null,
+			sections: [],
+
+			menu_items: [
+				{
+					label: __('Save Item'),
+					condition: !this.is_own_item,
+					action: this.add_to_saved_items
+				},
+				{
+					label: __('Report this Item'),
+					condition: !this.is_own_item,
+					action: this.report_item
+				},
+				{
+					label: __('Edit Details'),
+					condition: this.is_own_item,
+					action: this.edit_details
+				},
+				{
+					label: __('Unpublish Item'),
+					condition: this.is_own_item,
+					action: this.unpublish_item
+				}
+			]
+		};
+	},
+	computed: {
+		is_own_item() {
+			let is_own_item = false;
+			if(this.item) {
+				if(this.item.hub_seller === hub.setting.company_email) {
+					is_own_item = true;
+				}
+			}
+			return is_own_item;
+		},
+
+		item_subtitle() {
+			if(!this.item) {
+				return '';
+			}
+
+			const dot_spacer = '<span aria-hidden="true"> · </span>';
+			let subtitle_items = [comment_when(this.item.creation)];
+			const rating = this.item.average_rating;
+
+			if (rating > 0) {
+				subtitle_items.push(rating + `<i class='fa fa-fw fa-star-o'></i>`)
+			}
+
+			subtitle_items.push(this.item.company);
+
+			return subtitle_items;
+		},
+
+		item_views_and_ratings() {
+			if(!this.item) {
+				return '';
+			}
+
+			let stats = __('No views yet');
+			if(this.item.view_count) {
+				const views_message = __(`${this.item.view_count} Views`);
+
+				const rating_html = get_rating_html(this.item.average_rating);
+				const rating_count = this.item.no_of_ratings > 0 ? `${this.item.no_of_ratings} reviews` : __('No reviews yet');
+
+				stats = [views_message, rating_html, rating_count];
+			}
+
+			return stats;
+		},
+
+		primary_action() {
+			return {
+				label: __('Contact Seller'),
+				action: this.contact_seller.bind(this)
+			}
+		}
+	},
+	created() {
+		this.get_item_details();
+	},
+	mounted() {
+		// To record a single view per session, (later)
+		// erpnext.hub.item_view_cache = erpnext.hub.item_view_cache || [];
+		// if (erpnext.hub.item_view_cache.includes(this.hub_item_name)) {
+		// 	return;
+		// }
+
+		this.item_received.then(() => {
+			setTimeout(() => {
+				hub.call('add_item_view', {
+					hub_item_name: this.hub_item_name
+				})
+				// .then(() => {
+				// 	erpnext.hub.item_view_cache.push(this.hub_item_name);
+				// });
+			}, 5000);
+		});
+	},
+	methods: {
+		get_item_details() {
+			this.item_received = hub.call('get_item_details', { hub_item_name: this.hub_item_name })
+				.then(item => {
+				this.init = false;
+				this.item = item;
+
+				this.build_data();
+				this.make_dialogs();
+			});
+		},
+
+		build_data() {
+			this.title = this.item.item_name || this.item.name;
+			this.image = this.item.image;
+
+			this.sections = [
+				{
+					title: __('Item Description'),
+					content: this.item.description
+						? __(this.item.description)
+						: __('No description')
+				},
+				{
+					title: __('Seller Information'),
+					content: this.item.seller_description
+						? __(this.item.seller_description)
+						: __('No description')
+				}
+			];
+		},
+
+		make_dialogs() {
+			this.make_contact_seller_dialog();
+			this.make_report_item_dialog();
+		},
+
+		add_to_saved_items() {
+			hub.call('add_item_to_seller_saved_items', {
+				hub_item_name: this.hub_item_name,
+				hub_seller: hub.settings.company_email
+			})
+			.then(() => {
+				const saved_items_link = `<b><a href="#marketplace/saved-items">${__('Saved')}</a></b>`
+				frappe.show_alert(saved_items_link);
+				erpnext.hub.trigger('action:item_save');
+			})
+			.catch(e => {
+				console.error(e);
+			});
+		},
+
+		make_contact_seller_dialog() {
+			this.contact_seller_dialog = new frappe.ui.Dialog({
+				title: __('Send a message'),
+				fields: [
+					{
+						fieldname: 'to',
+						fieldtype: 'Read Only',
+						label: __('To'),
+						default: this.item.company
+					},
+					{
+						fieldtype: 'Text',
+						fieldname: 'message',
+						label: __('Message')
+					}
+				],
+				primary_action: ({ message }) => {
+					if (!message) return;
+
+					hub.call('send_message', {
+						from_seller: hub.settings.company_email,
+						to_seller: this.item.hub_seller,
+						hub_item: this.item.name,
+						message
+					})
+						.then(() => {
+							d.hide();
+							frappe.set_route('marketplace', 'buying', this.item.name);
+							erpnext.hub.trigger('action:send_message')
+						});
+				}
+			});
+		},
+
+		make_report_item_dialog() {
+			this.report_item_dialog = new frappe.ui.Dialog({
+				title: __('Report Item'),
+				fields: [
+					{
+						label: __('Why do think this Item should be removed?'),
+						fieldtype: 'Text',
+						fieldname: 'message'
+					}
+				],
+				primary_action: ({ message }) => {
+					hub.call('add_reported_item', { hub_item_name: this.item.name, message })
+						.then(() => {
+							d.hide();
+							frappe.show_alert(__('Item Reported'));
+						});
+				}
+			});
+		},
+
+		contact_seller() {
+			this.contact_seller_dialog.show();
+		},
+
+		report_item() {
+			this.report_item_dialog.show();
+		},
+
+		edit_details() {
+			//
+		},
+
+		unpublish_item() {
+			//
+		}
+	}
+}
+</script>
+
+<style scoped></style>
diff --git a/erpnext/public/js/hub/pages/Messages.vue b/erpnext/public/js/hub/pages/Messages.vue
new file mode 100644
index 0000000..1930bcb
--- /dev/null
+++ b/erpnext/public/js/hub/pages/Messages.vue
@@ -0,0 +1,105 @@
+<template>
+	<div v-if="item_details">
+		<div>
+			<a class="text-muted" v-route="back_link">← {{ __('Back to Messages') }}</a>
+		</div>
+		<section-header>
+			<div class="flex flex-column margin-bottom">
+				<h4>{{ item_details.item_name }}</h4>
+				<span class="text-muted">{{ item_details.company }}</span>
+			</div>
+		</section-header>
+		<div class="row">
+			<div class="col-md-7">
+				<div class="message-container">
+					<div class="message-list">
+						<div class="level margin-bottom" v-for="message in messages" :key="message.name">
+							<div class="level-left ellipsis" style="width: 80%;">
+								<div v-html="frappe.avatar(message.sender)" />
+								<div style="white-space: normal;" v-html="message.content" />
+							</div>
+							<div class="level-right text-muted" v-html="frappe.datetime.comment_when(message.creation, true)" />
+						</div>
+					</div>
+					<div class="message-input">
+						<comment-input @change="send_message" />
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</template>
+<script>
+import CommentInput from '../components/CommentInput.vue';
+import ItemListCard from '../components/ItemListCard.vue';
+
+export default {
+	components: {
+		CommentInput,
+		ItemListCard
+	},
+	data() {
+		return {
+			message_type: frappe.get_route()[1],
+			item_details: null,
+			messages: []
+		}
+	},
+	created() {
+		const hub_item_name = this.get_hub_item_name();
+		this.get_item_details(hub_item_name)
+			.then(item_details => {
+				this.item_details = item_details;
+				this.get_messages()
+					.then(messages => {
+						this.messages = messages;
+					});
+			});
+	},
+	computed: {
+		back_link() {
+			return 'marketplace/' + this.message_type;
+		}
+	},
+	methods: {
+		send_message(message) {
+			this.messages.push({
+				sender: hub.settings.company_email,
+				content: message,
+				creation: Date.now(),
+				name: frappe.utils.get_random(6)
+			});
+			hub.call('send_message', {
+				from_seller: hub.settings.company_email,
+				to_seller: this.get_against_seller(),
+				hub_item: this.item_details.name,
+				message
+			});
+		},
+		get_item_details(hub_item_name) {
+			return hub.call('get_item_details', { hub_item_name })
+		},
+		get_messages() {
+			if (!this.item_details) return [];
+			return hub.call('get_messages', {
+				against_seller: this.get_against_seller(),
+				against_item: this.item_details.name
+			});
+		},
+		get_against_seller() {
+			if (this.message_type === 'buying') {
+				return this.item_details.hub_seller;
+			} else if (this.message_type === 'selling') {
+				return frappe.get_route()[2];
+			}
+		},
+		get_hub_item_name() {
+			if (this.message_type === 'buying') {
+				return frappe.get_route()[2];
+			} else if (this.message_type === 'selling') {
+				return frappe.get_route()[3];
+			}
+		}
+	}
+}
+</script>
diff --git a/erpnext/public/js/hub/pages/NotFound.vue b/erpnext/public/js/hub/pages/NotFound.vue
new file mode 100644
index 0000000..246d31b
--- /dev/null
+++ b/erpnext/public/js/hub/pages/NotFound.vue
@@ -0,0 +1,36 @@
+<template>
+	<div
+		class="marketplace-page"
+		:data-page-name="page_name"
+	>
+		<empty-state
+			:message="empty_state_message"
+			:height="500"
+			:action="action"
+		>
+		</empty-state>
+
+	</div>
+</template>
+
+<script>
+export default {
+	name: 'not-found-page',
+	data() {
+		return {
+			page_name: 'not-found',
+			action: {
+				label: __('Back to Home'),
+				on_click: () => {
+					frappe.set_route(`marketplace/home`);
+				}
+			},
+
+			// Constants
+			empty_state_message: __(`Sorry! I could not find what you were looking for.`)
+		};
+	},
+}
+</script>
+
+<style scoped></style>
diff --git a/erpnext/public/js/hub/pages/Profile.vue b/erpnext/public/js/hub/pages/Profile.vue
new file mode 100644
index 0000000..a0bc6cb
--- /dev/null
+++ b/erpnext/public/js/hub/pages/Profile.vue
@@ -0,0 +1,81 @@
+<template>
+	<div
+		class="marketplace-page"
+		:data-page-name="page_name"
+		v-if="init || profile"
+	>
+
+		<detail-view
+			:title="title"
+			:image="image"
+			:sections="sections"
+			:show_skeleton="init"
+		>
+
+			<detail-header-item slot="detail-header-item"
+				:value="country"
+			></detail-header-item>
+			<detail-header-item slot="detail-header-item"
+				:value="site_name"
+			></detail-header-item>
+			<detail-header-item slot="detail-header-item"
+				:value="joined_when"
+			></detail-header-item>
+
+		</detail-view>
+	</div>
+</template>
+
+<script>
+export default {
+	name: 'profile-page',
+	data() {
+		return {
+			page_name: frappe.get_route()[1],
+
+			init: true,
+
+			profile: null,
+			title: null,
+			image: null,
+			sections: [],
+
+			country: '',
+			site_name: '',
+			joined_when: '',
+		};
+	},
+	created() {
+		this.get_profile();
+	},
+	methods: {
+		get_profile() {
+			hub.call(
+				'get_hub_seller_profile',
+				{ hub_seller: hub.settings.company_email }
+			).then(profile => {
+				this.init = false;
+
+				this.profile = profile;
+				this.title = profile.company;
+
+				this.country = __(profile.country);
+				this.site_name = __(profile.site_name);
+				this.joined_when = __(`Joined ${comment_when(profile.creation)}`);
+
+				this.image = profile.logo;
+				this.sections = [
+					{
+						title: __('About the Company'),
+						content: profile.company_description
+							? __(profile.company_description)
+							: __('No description')
+					}
+				];
+			});
+		}
+	}
+}
+</script>
+
+<style scoped></style>
diff --git a/erpnext/public/js/hub/pages/Publish.vue b/erpnext/public/js/hub/pages/Publish.vue
new file mode 100644
index 0000000..b05f12a
--- /dev/null
+++ b/erpnext/public/js/hub/pages/Publish.vue
@@ -0,0 +1,217 @@
+<template>
+	<div
+		class="marketplace-page"
+		:data-page-name="page_name"
+	>
+		<notification-message
+			v-if="last_sync_message"
+			:message="last_sync_message"
+			@remove-message="clear_last_sync_message"
+		></notification-message>
+
+		<div class="flex justify-between align-flex-end margin-bottom">
+			<h5>{{ page_title }}</h5>
+
+			<button class="btn btn-primary btn-sm publish-items"
+				:disabled="no_selected_items"
+				@click="publish_selected_items"
+			>
+				<span>{{ publish_button_text }}</span>
+			</button>
+		</div>
+
+		<item-cards-container
+			:container_name="page_title"
+			:items="selected_items"
+			:item_id_fieldname="item_id_fieldname"
+			:is_local="true"
+			:editable="true"
+			@remove-item="remove_item_from_selection"
+
+			:empty_state_message="empty_state_message"
+			:empty_state_bordered="true"
+			:empty_state_height="80"
+		>
+		</item-cards-container>
+
+		<p class="text-muted">{{ valid_items_instruction }}</p>
+
+		<search-input
+			:placeholder="search_placeholder"
+			:on_search="get_valid_items"
+			v-model="search_value"
+		>
+		</search-input>
+
+		<item-cards-container
+			:items="valid_items"
+			:item_id_fieldname="item_id_fieldname"
+			:is_local="true"
+			:on_click="show_publishing_dialog_for_item"
+		>
+		</item-cards-container>
+	</div>
+</template>
+
+<script>
+import NotificationMessage from '../components/NotificationMessage.vue';
+import { ItemPublishDialog } from '../components/item_publish_dialog';
+
+export default {
+	name: 'publish-page',
+	components: {
+		NotificationMessage
+	},
+	data() {
+		return {
+			page_name: frappe.get_route()[1],
+			valid_items: [],
+			selected_items: [],
+			items_data_to_publish: {},
+			search_value: '',
+			item_id_fieldname: 'item_code',
+
+			// Constants
+			// TODO: multiline translations don't work
+			page_title: __('Publish Items'),
+			search_placeholder: __('Search Items ...'),
+			empty_state_message: __(`No Items selected yet. Browse and click on items below to publish.`),
+			valid_items_instruction: __(`Only items with an image and description can be published. Please update them if an item in your inventory does not appear.`),
+			last_sync_message: (hub.settings.last_sync_datetime)
+				? __(`Last sync was
+				<a href="#marketplace/profile">
+					${comment_when(hub.settings.last_sync_datetime)}</a>.
+				<a href="#marketplace/published-items">
+					See your Published Items</a>.`)
+				: ''
+		};
+	},
+	computed: {
+		no_selected_items() {
+			return this.selected_items.length === 0;
+		},
+
+		publish_button_text() {
+			const number = this.selected_items.length;
+			let text = __('Publish');
+			if(number === 1) {
+				text = __('Publish 1 Item');
+			}
+			if(number > 1) {
+				text = __('Publish {0} Items', [number]);
+			}
+			return text;
+		},
+
+		items_dict() {
+			let items_dict = {};
+			this.valid_items.map(item => {
+				items_dict[item[this.item_id_fieldname]] = item
+			})
+
+			return items_dict;
+		},
+	},
+	created() {
+		this.get_valid_items();
+		this.make_publishing_dialog();
+	},
+	methods: {
+		get_valid_items() {
+			frappe.call(
+				'erpnext.hub_node.api.get_valid_items',
+				{
+					search_value: this.search_value
+				}
+			)
+			.then((r) => {
+				this.valid_items = r.message;
+			})
+		},
+
+		publish_selected_items() {
+			frappe.call(
+			'erpnext.hub_node.api.publish_selected_items',
+				{
+					items_to_publish: this.selected_items
+				}
+			)
+			.then((r) => {
+				this.selected_items = [];
+				return frappe.db.get_doc('Hub Settings');
+			})
+			.then(doc => {
+				hub.settings = doc;
+				this.add_last_sync_message();
+			});
+		},
+
+		add_last_sync_message() {
+			this.last_sync_message = __(`Last sync was
+				<a href="#marketplace/profile">
+					${comment_when(hub.settings.last_sync_datetime)}</a>.
+				<a href="#marketplace/published-items">
+					See your Published Items</a>.`);
+		},
+
+		clear_last_sync_message() {
+			this.last_sync_message = '';
+		},
+
+		remove_item_from_selection(item_code) {
+			this.selected_items = this.selected_items
+				.filter(item => item.item_code !== item_code);
+		},
+
+		make_publishing_dialog() {
+			this.item_publish_dialog = ItemPublishDialog(
+				{
+					fn: (values) => {
+						this.add_item_to_publish(values);
+						this.item_publish_dialog.hide();
+					}
+				},
+				{
+					fn: () => {
+						const values = this.item_publish_dialog.get_values(true);
+						this.update_items_data_to_publish(values);
+					}
+				}
+			);
+		},
+
+		add_item_to_publish(values) {
+			this.update_items_data_to_publish(values);
+
+			const item_code  = values.item_code;
+			let item_doc = this.items_dict[item_code];
+
+			const item_to_publish = Object.assign({}, item_doc, values);
+			this.selected_items.push(item_to_publish);
+		},
+
+		update_items_data_to_publish(values) {
+			this.items_data_to_publish[values.item_code] = values;
+		},
+
+		show_publishing_dialog_for_item(item_code) {
+			let item_data = this.items_data_to_publish[item_code];
+			if(!item_data) { item_data = { item_code }; };
+
+			this.item_publish_dialog.clear();
+
+			const item_doc = this.items_dict[item_code];
+			if(item_doc) {
+				this.item_publish_dialog.fields_dict.image_list.set_data(
+					item_doc.attachments.map(attachment => attachment.file_url)
+				);
+			}
+
+			this.item_publish_dialog.set_values(item_data);
+			this.item_publish_dialog.show();
+		}
+	}
+}
+</script>
+
+<style scoped></style>
diff --git a/erpnext/public/js/hub/pages/PublishedItems.vue b/erpnext/public/js/hub/pages/PublishedItems.vue
new file mode 100644
index 0000000..114259b
--- /dev/null
+++ b/erpnext/public/js/hub/pages/PublishedItems.vue
@@ -0,0 +1,81 @@
+<template>
+	<div
+		class="marketplace-page"
+		:data-page-name="page_name"
+	>
+		<section-header>
+			<div>
+				<h5>{{ page_title }}</h5>
+				<p v-if="items.length"
+					class="text-muted margin-bottom">
+					{{ published_items_message }}
+				</p>
+			</div>
+
+			<button v-if="items.length"
+				class="btn btn-default btn-xs publish-items"
+				v-route="'marketplace/publish'"
+			>
+				<span>{{ publish_button_text }}</span>
+			</button>
+
+		</section-header>
+
+		<item-cards-container
+			:container_name="page_title"
+			:items="items"
+			:item_id_fieldname="item_id_fieldname"
+			:on_click="go_to_item_details_page"
+			:empty_state_message="empty_state_message"
+			:empty_state_action="publish_page_action"
+		>
+		</item-cards-container>
+	</div>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			page_name: frappe.get_route()[1],
+			items: [],
+			item_id_fieldname: 'name',
+
+			publish_page_action: {
+				label: __('Publish Your First Items'),
+				on_click: () => {
+					frappe.set_route(`marketplace/home`);
+				}
+			},
+
+			// Constants
+			page_title: __('Published Items'),
+			publish_button_text: __('Publish More Items'),
+			published_items_message: __('You can publish upto 200 items.'),
+			// TODO: Add empty state action
+			empty_state_message: __('You haven\'t published any items yet.')
+		};
+	},
+	created() {
+		this.get_items();
+	},
+	methods: {
+		get_items() {
+			hub.call('get_items', {
+				filters: {
+					hub_seller: hub.settings.company_email
+				}
+			})
+			.then((items) => {
+				this.items = items;
+			})
+		},
+
+		go_to_item_details_page(hub_item_name) {
+			frappe.set_route(`marketplace/item/${hub_item_name}`);
+		}
+	}
+}
+</script>
+
+<style scoped></style>
diff --git a/erpnext/public/js/hub/pages/SavedItems.vue b/erpnext/public/js/hub/pages/SavedItems.vue
new file mode 100644
index 0000000..e5d21cd
--- /dev/null
+++ b/erpnext/public/js/hub/pages/SavedItems.vue
@@ -0,0 +1,111 @@
+<template>
+	<div
+		class="marketplace-page"
+		:data-page-name="page_name"
+	>
+		<h5>{{ page_title }}</h5>
+
+		<item-cards-container
+			:container_name="page_title"
+			:items="items"
+			:item_id_fieldname="item_id_fieldname"
+			:on_click="go_to_item_details_page"
+			:editable="true"
+			@remove-item="on_item_remove"
+			:empty_state_message="empty_state_message"
+		>
+		</item-cards-container>
+	</div>
+</template>
+
+<script>
+export default {
+	name: 'saved-items-page',
+	data() {
+		return {
+			page_name: frappe.get_route()[1],
+			items: [],
+			item_id_fieldname: 'name',
+
+			// Constants
+			page_title: __('Saved Items'),
+			empty_state_message: __(`You haven't saved any items yet.`)
+		};
+	},
+	created() {
+		this.get_items();
+	},
+	methods: {
+		get_items() {
+			hub.call(
+				'get_saved_items_of_seller', {},
+				'action:item_save'
+			)
+			.then((items) => {
+				this.items = items;
+			})
+		},
+
+		go_to_item_details_page(hub_item_name) {
+			frappe.set_route(`marketplace/item/${hub_item_name}`);
+		},
+
+		on_item_remove(hub_item_name) {
+			const grace_period = 5000;
+			let reverted = false;
+			let alert;
+
+			const undo_remove = () => {
+				this.toggle_item(hub_item_name);;
+				reverted = true;
+				alert.hide();
+				return false;
+			}
+
+			const item_name = this.items.filter(item => item.hub_item_name === hub_item_name);
+
+			alert = frappe.show_alert(__(`<span>${item_name} removed.
+				<a href="#" data-action="undo-remove"><b>Undo</b></a></span>`),
+				grace_period/1000,
+				{
+					'undo-remove': undo_remove.bind(this)
+				}
+			);
+
+			this.toggle_item(hub_item_name, false);
+
+			setTimeout(() => {
+				if(!reverted) {
+					this.remove_item_from_saved_items(hub_item_name);
+				}
+			}, grace_period);
+		},
+
+		remove_item_from_saved_items(hub_item_name) {
+			erpnext.hub.trigger('action:item_save');
+			hub.call('remove_item_from_seller_saved_items', {
+				hub_item_name,
+				hub_seller: hub.settings.company_email
+			})
+			.then(() => {
+				this.get_items();
+			})
+			.catch(e => {
+				console.log(e);
+			});
+		},
+
+		// By default show
+		toggle_item(hub_item_name, show=true) {
+			this.items = this.items.map(item => {
+				if(item.name === hub_item_name) {
+					item.seen = show;
+				}
+				return item;
+			});
+		}
+	}
+}
+</script>
+
+<style scoped></style>
diff --git a/erpnext/public/js/hub/pages/Search.vue b/erpnext/public/js/hub/pages/Search.vue
new file mode 100644
index 0000000..5118a81
--- /dev/null
+++ b/erpnext/public/js/hub/pages/Search.vue
@@ -0,0 +1,70 @@
+<template>
+	<div
+		class="marketplace-page"
+		:data-page-name="page_name"
+	>
+		<search-input
+			:placeholder="search_placeholder"
+			:on_search="set_route_and_get_items"
+			v-model="search_value"
+		>
+		</search-input>
+
+		<h5>{{ page_title }}</h5>
+
+		<item-cards-container
+			container_name="Search"
+			:items="items"
+			:item_id_fieldname="item_id_fieldname"
+			:on_click="go_to_item_details_page"
+			:empty_state_message="empty_state_message"
+		>
+		</item-cards-container>
+	</div>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			page_name: frappe.get_route()[1],
+			items: [],
+			search_value: frappe.get_route()[2],
+			item_id_fieldname: 'name',
+
+			// Constants
+			search_placeholder: __('Search for anything ...'),
+			empty_state_message: __('')
+		};
+	},
+	computed: {
+		page_title() {
+			return this.items.length
+				? __(`Results for "${this.search_value}"`)
+				: __('No Items found.');
+		}
+	},
+	created() {
+		this.get_items();
+	},
+	methods: {
+		get_items() {
+			hub.call('get_items', { keyword: this.search_value })
+			.then((items) => {
+				this.items = items;
+			})
+		},
+
+		set_route_and_get_items() {
+			frappe.set_route('marketplace', 'search', this.search_value);
+			this.get_items();
+		},
+
+		go_to_item_details_page(hub_item_name) {
+			frappe.set_route(`marketplace/item/${hub_item_name}`);
+		}
+	}
+}
+</script>
+
+<style scoped></style>
diff --git a/erpnext/public/js/hub/pages/Seller.vue b/erpnext/public/js/hub/pages/Seller.vue
new file mode 100644
index 0000000..c80865b
--- /dev/null
+++ b/erpnext/public/js/hub/pages/Seller.vue
@@ -0,0 +1,104 @@
+<template>
+	<div
+		class="marketplace-page"
+		:data-page-name="page_name"
+		v-if="init || profile"
+	>
+		<detail-view
+			:title="title"
+			:image="image"
+			:sections="sections"
+			:show_skeleton="init"
+		>
+			<detail-header-item slot="detail-header-item"
+				:value="country"
+			></detail-header-item>
+			<detail-header-item slot="detail-header-item"
+				:value="site_name"
+			></detail-header-item>
+			<detail-header-item slot="detail-header-item"
+				:value="joined_when"
+			></detail-header-item>
+
+		</detail-view>
+
+		<h5 v-if="profile">{{ item_container_heading }}</h5>
+		<item-cards-container
+			:container_name="item_container_heading"
+			:items="items"
+			:item_id_fieldname="item_id_fieldname"
+			:on_click="go_to_item_details_page"
+		>
+		</item-cards-container>
+	</div>
+</template>
+
+<script>
+export default {
+	name: 'seller-page',
+	data() {
+		return {
+			page_name: frappe.get_route()[1],
+			seller_company: frappe.get_route()[2],
+
+			init: true,
+
+			profile: null,
+			items:[],
+			item_id_fieldname: 'name',
+
+			title: null,
+			image: null,
+			sections: [],
+
+			country: '',
+			site_name: '',
+			joined_when: '',
+		};
+	},
+	created() {
+		this.get_seller_profile_and_items();
+	},
+	computed: {
+		item_container_heading() {
+			return __('Items by ' + this.seller_company);
+		}
+	},
+	methods: {
+		get_seller_profile_and_items() {
+			hub.call(
+				'get_hub_seller_page_info',
+				{ company: this.seller_company }
+			).then(data => {
+				this.init = false;
+				this.profile = data.profile;
+				this.items = data.items;
+
+				const profile = this.profile;
+
+				this.title = profile.company;
+
+				this.country = __(profile.country);
+				this.site_name = __(profile.site_name);
+				this.joined_when = __(`Joined ${comment_when(profile.creation)}`);
+
+				this.image = profile.logo;
+				this.sections = [
+					{
+						title: __('About the Company'),
+						content: profile.company_description
+							? __(profile.company_description)
+							: __('No description')
+					}
+				];
+			});
+		},
+
+		go_to_item_details_page(hub_item_name) {
+			frappe.set_route(`marketplace/item/${hub_item_name}`);
+		}
+	}
+}
+</script>
+
+<style scoped></style>
diff --git a/erpnext/public/js/hub/pages/Selling.vue b/erpnext/public/js/hub/pages/Selling.vue
new file mode 100644
index 0000000..9c8ede6
--- /dev/null
+++ b/erpnext/public/js/hub/pages/Selling.vue
@@ -0,0 +1,66 @@
+<template>
+	<div>
+		<section-header>
+			<h4>{{ __('Selling') }}</h4>
+		</section-header>
+		<div class="row" v-if="items && items.length">
+			<div class="col-md-7"
+				style="margin-bottom: 30px;"
+				v-for="item of items"
+				:key="item.name"
+			>
+				<item-list-card
+					:item="item"
+				>
+					<div slot="subtitle">
+						<span class="text-muted">{{ __('{0} conversations', [item.received_messages.length]) }}</span>
+					</div>
+				</item-list-card>
+				<div class="hub-list-item" v-for="(message, index) in item.received_messages" :key="index"
+					v-route="'marketplace/selling/' + message.buyer_email + '/' + item.name"
+				>
+					<div class="hub-list-left">
+						<div class="hub-list-body">
+							<div class="hub-list-title">
+								{{ message.buyer }}
+							</div>
+							<div class="hub-list-subtitle">
+								{{ message.sender }}: {{ message.content }}
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+		<empty-state v-else :message="__('This page keeps track of your items in which buyers have showed some interest.')" :centered="false" />
+	</div>
+</template>
+<script>
+import EmptyState from '../components/EmptyState.vue';
+import SectionHeader from '../components/SectionHeader.vue';
+import ItemListCard from '../components/ItemListCard.vue';
+
+export default {
+	components: {
+		SectionHeader,
+		ItemListCard,
+		EmptyState
+	},
+	data() {
+		return {
+			items: null
+		}
+	},
+	created() {
+		this.get_items_for_messages()
+			.then(items => {
+				this.items = items;
+			});
+	},
+	methods: {
+		get_items_for_messages() {
+			return hub.call('get_selling_items_for_messages');
+		}
+	}
+}
+</script>
diff --git a/erpnext/public/js/hub/vue-plugins.js b/erpnext/public/js/hub/vue-plugins.js
new file mode 100644
index 0000000..e18af0c
--- /dev/null
+++ b/erpnext/public/js/hub/vue-plugins.js
@@ -0,0 +1,66 @@
+import Vue from 'vue/dist/vue.js';
+
+// Global components
+import ItemCardsContainer from './components/ItemCardsContainer.vue';
+import SectionHeader from './components/SectionHeader.vue';
+import SearchInput from './components/SearchInput.vue';
+import DetailView from './components/DetailView.vue';
+import DetailHeaderItem from './components/DetailHeaderItem.vue';
+import EmptyState from './components/EmptyState.vue';
+
+Vue.prototype.__ = window.__;
+Vue.prototype.frappe = window.frappe;
+
+Vue.component('item-cards-container', ItemCardsContainer);
+Vue.component('section-header', SectionHeader);
+Vue.component('search-input', SearchInput);
+Vue.component('detail-view', DetailView);
+Vue.component('detail-header-item', DetailHeaderItem);
+Vue.component('empty-state', EmptyState);
+
+Vue.directive('route', {
+	bind(el, binding) {
+		const route = binding.value;
+		if (!route) return;
+		el.classList.add('cursor-pointer');
+		el.dataset.route = route;
+		el.addEventListener('click', () => frappe.set_route(route));
+	},
+	unbind(el) {
+		el.classList.remove('cursor-pointer');
+	}
+});
+
+const handleImage = (el, src) => {
+	let img = new Image();
+	// add loading class
+	el.src = '';
+	el.classList.add('img-loading');
+
+	img.onload = () => {
+		// image loaded, remove loading class
+		el.classList.remove('img-loading');
+		// set src
+		el.src = src;
+	}
+	img.onerror = () => {
+		el.classList.remove('img-loading');
+		el.classList.add('no-image');
+		el.src = null;
+	}
+	img.src = src;
+}
+
+Vue.directive('img-src', {
+	bind(el, binding) {
+		handleImage(el, binding.value);
+	},
+	update(el, binding) {
+		if (binding.value === binding.oldValue) return;
+		handleImage(el, binding.value);
+	}
+});
+
+Vue.filter('striphtml', function (text) {
+	return strip_html(text);
+});
\ No newline at end of file
diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less
index bdca28f..d40926b 100644
--- a/erpnext/public/less/hub.less
+++ b/erpnext/public/less/hub.less
@@ -1,171 +1,352 @@
 @import "../../../../frappe/frappe/public/less/variables.less";
 
-body[data-route^="Hub/"] {
-	.hub-icon {
-		width: 40px;
-		height: 40px;
+body[data-route^="marketplace/"] {
+	.layout-side-section {
+		padding-top: 25px;
+		padding-left: 5px;
+		padding-right: 25px;
 	}
 
-	.hub-page-title {
-		margin-left: 10px;
+	[data-route], [data-action] {
+		cursor: pointer;
 	}
 
-	.img-wrapper {
-		border: 1px solid #d1d8dd;
-		border-radius: 3px;
-		padding: 12px;
-		overflow: hidden;
-		text-align: center;
-		white-space: nowrap;
+	.layout-main-section {
+		border: none;
+		font-size: @text-medium;
+		padding-top: 25px;
 
-		.helper {
-			height: 100%;
-			display: inline-block;
-			vertical-align: middle;
+		@media (max-width: @screen-xs) {
+			padding-left: 20px;
+			padding-right: 20px;
 		}
 	}
 
-	.tree {
-		margin: 10px 0px;
-		padding: 0px;
-		height: 100%;
+	input, textarea {
+		font-size: @text-medium;
+	}
+
+	.progress-bar {
+		background-color: #89da28;
+	}
+
+	.subpage-title.flex {
+		align-items: flex-start;
+		justify-content: space-between;
+	}
+
+	.hub-card {
+		margin-bottom: 25px;
 		position: relative;
-	}
-
-	.tree.with-skeleton.opened::before {
-		left: 9px;
-		top: 14px;
-		height: calc(~"100% - 32px");
-	}
-
-	.list-header-icon {
-		width: 72px;
-		border-radius: 4px;
-		flex-shrink: 0;
-		margin: 10px;
-		padding: 1px;
 		border: 1px solid @border-color;
-		height: 72px;
+		border-radius: 4px;
+		overflow: hidden;
+
+		&:hover .hub-card-overlay {
+			display: block;
+		}
+	}
+
+	.hub-card.is-local {
+		&.active {
+			.hub-card-header {
+				background-color: #f4ffe5;
+			}
+
+			.octicon-check {
+				display: inline;
+			}
+		}
+
+		.octicon-check {
+			display: none;
+			position: absolute;
+			font-size: 20px;
+			right: 15px;
+			top: 50%;
+			transform: translateY(-50%);
+		}
+	}
+
+	.hub-card-header {
+		position: relative;
+		padding: 12px 15px;
+		height: 60px;
+		border-bottom: 1px solid @border-color;
+	}
+
+	.hub-card-body {
+		position: relative;
+		height: 200px;
+	}
+
+	.hub-card-overlay {
+		display: none;
+		position: absolute;
+		top: 0;
+		width: 100%;
+		height: 100%;
+		background-color: rgba(0, 0, 0, 0.05);
+	}
+
+	.hub-card-overlay-body {
+		position: relative;
+		height: 100%;
+	}
+
+	.hub-card-overlay-button {
+		position: absolute;
+		right: 15px;
+		bottom: 15px;
+	}
+
+	.hub-card-image {
+		position: relative;
+		width: 100%;
+		height: 100%;
+		object-fit: contain;
+	}
+
+	.hub-search-container {
+		margin-bottom: 20px;
+
+		input {
+			height: 32px;
+		}
+	}
+
+	.hub-sidebar {
+		padding-top: 25px;
+		padding-right: 15px;
+	}
+
+	.hub-sidebar-group {
+		margin-bottom: 10px;
+	}
+
+	.hub-sidebar-item {
+		padding: 5px 8px;
+		margin-bottom: 3px;
+		border-radius: 4px;
+		border: 1px solid transparent;
+
+		&.active, &:hover:not(.is-title) {
+			border-color: @border-color;
+		}
+	}
+
+	.hub-item-image {
+		border: 1px solid @border-color;
+		border-radius: 4px;
+		overflow: hidden;
+		height: 200px;
+		width: 200px;
 		display: flex;
 		align-items: center;
-		justify-content: center;
+	}
 
-		img {
-			border-radius: 4px;
+	.hub-item-skeleton-image {
+		border-radius: 4px;
+		background-color: @light-bg;
+		overflow: hidden;
+		height: 200px;
+		width: 200px;
+	}
+
+	.hub-skeleton {
+		background-color: @light-bg;
+		color: @light-bg;
+		max-width: 500px;
+	}
+
+	.hub-item-seller img {
+		width: 50px;
+		height: 50px;
+		border-radius: 4px;
+		border: 1px solid @border-color;
+	}
+
+	.register-title {
+		font-size: @text-regular;
+	}
+
+	.register-form {
+		border: 1px solid @border-color;
+		border-radius: 4px;
+		padding: 15px 25px;
+	}
+
+	.publish-area.filled {
+		.empty-items-container {
+			display: none;
 		}
 	}
 
-	.star-icon.fa-star {
-		color: @indicator-orange;
+	.publish-area.empty {
+		.hub-items-container {
+			display: none;
+		}
 	}
 
-	.octicon-heart.liked {
-		color: @indicator-red;
+	.publish-area-head {
+		display: flex;
+		justify-content: space-between;
+		margin-bottom: 20px;
 	}
 
-	.margin-vertical-10 {
-		margin: 10px 0px;
+	.hub-list-item {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		border: 1px solid @border-color;
+		margin-bottom: -1px;
+		overflow: hidden;
 	}
 
-	.margin-vertical-15 {
-		margin: 15px 0px;
+	.hub-list-item:first-child {
+		border-top-left-radius: 4px;
+		border-top-right-radius: 4px;
+	}
+	.hub-list-item:last-child {
+		border-bottom-left-radius: 4px;
+		border-bottom-right-radius: 4px;
 	}
 
-	.frappe-list .result {
-		min-height: 100px;
+	.hub-list-left {
+		display: flex;
+		align-items: center;
+		max-width: 90%;
 	}
 
-	.frappe-control[data-fieldtype="Attach Image"] {
-		width: 140px;
-		height: 180px;
+	.hub-list-right {
+		padding-right: 15px;
+	}
+
+	.hub-list-image {
+		position: relative;
+		width: 58px;
+		height: 58px;
+		border-right: 1px solid @border-color;
+
+		&::after {
+			font-size: 12px;
+		}
+	}
+
+	.hub-list-body {
+		padding: 12px 15px;
+	}
+
+	.hub-list-title {
+		font-weight: bold;
+	}
+
+	.hub-list-subtitle {
+		color: @text-muted;
+	}
+
+	.selling-item-message-card {
+		max-width: 500px;
+		margin-bottom: 15px;
+		border-radius: 3px;
+		border: 1px solid @border-color;
+		.selling-item-detail {
+			overflow: auto;
+			.item-image {
+				float: left;
+				height: 80px;
+				width: 80px;
+				object-fit: contain;
+				margin: 5px;
+			}
+			.item-name {
+				margin-left: 10px;
+			}
+		}
+		.received-message-container {
+			clear: left;
+			background-color: @light-bg;
+			.received-message {
+				border-top: 1px solid @border-color;
+				padding: 10px;
+			}
+			.frappe-timestamp {
+				float: right;
+			}
+		}
+	}
+
+	.form-container {
+		.frappe-control {
+			max-width: 100% !important;
+		}
+	}
+
+	.form-message {
+		padding-top: 0;
+		padding-bottom: 0;
+		border-bottom: none;
+	}
+
+	.hub-items-container {
+		.hub-items-header {
+			justify-content: space-between;
+			align-items: baseline;
+		}
+	}
+
+	.hub-item-container {
+		overflow: hidden;
+	}
+
+	.hub-item-review-container {
+		margin-top: calc(30vh);
+	}
+
+	.hub-item-dropdown {
 		margin-top: 20px;
 	}
 
-	.frappe-control[data-fieldtype="Attach Image"] .form-group {
-		display: none;
-	}
+	/* messages page */
 
-	.frappe-control[data-fieldtype="Attach Image"] .clearfix {
-		display: none;
-	}
-
-	.missing-image {
-		display: block;
-		position: relative;
-		border-radius: 4px;
-		border: 1px solid #d1d8dd;
-		border-radius: 6px;
-		box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-	}
-	.missing-image .octicon {
-		position: relative;
-		top: 50%;
-		transform: translate(0px, -50%);
-		-webkit-transform: translate(0px, -50%);
-	}
-	.attach-image-display {
-		display: block;
-		position: relative;
-		border-radius: 4px;
-	}
-	.img-container {
-		height: 100%;
-		width: 100%;
-		padding: 2px;
+	.message-list-item {
 		display: flex;
 		align-items: center;
-		justify-content: center;
-		position: relative;
-		border: 1px solid #d1d8dd;
-		border-radius: 6px;
-		box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-	}
-	.img-overlay {
-		display: flex;
-		align-items: center;
-		justify-content: center;
-		position: absolute;
-		width: 100%;
-		height: 100%;
-		color: #777777;
-		background-color: rgba(255, 255, 255, 0.7);
-		opacity: 0;
-	}
-	.img-overlay:hover {
-		opacity: 1;
-		cursor: pointer;
-	}
-}
+		padding: 8px 12px;
 
-.image-view-container {
-	.image-view-body {
-		&:hover .like-button {
-			opacity: 0.7;
+		&:not(.active) {
+			filter: grayscale(1);
+			color: @text-muted;
+		}
+
+		&:hover {
+			background-color: @light-bg;
+		}
+
+		.list-item-left {
+			width: 30px;
+			border-radius: 4px;
+			overflow: hidden;
+			margin-right: 15px;
+		}
+
+		.list-item-body {
+			font-weight: bold;
+			padding-bottom: 1px;
 		}
 	}
 
-	.like-button {
-		bottom: 10px !important;
-		left: 10px !important;
-		width: 36px;
-		height: 36px;
-		opacity: 0;
-		font-size: 16px;
-		color: @text-color;
-		position: absolute;
-
-		// show zoom button on mobile devices
-		@media (max-width: @screen-xs) {
-			opacity: 0.5
-		}
+	.message-container {
+		display: flex;
+		flex-direction: column;
+		border: 1px solid @border-color;
+		border-radius: 3px;
+		height: calc(100vh - 300px);
+		justify-content: space-between;
+		padding: 15px;
 	}
 
-	.image-view-body:hover .like-button {
-		opacity: 0.7;
+	.message-list {
+		overflow: scroll;
 	}
 }
-
-.rating-area .star-icon {
-	cursor: pointer;
-	font-size: 15px;
-}
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index 779f1c6..9161f2d 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -4121,4 +4121,4 @@
  "track_changes": 1,
  "track_seen": 0,
  "track_views": 0
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 2cf33b9..c79d76d 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -76,8 +76,8 @@
 		if not self.description:
 			self.description = self.item_name
 
-		if self.is_sales_item and not self.get('is_item_from_hub'):
-			self.publish_in_hub = 1
+		# if self.is_sales_item and not self.get('is_item_from_hub'):
+		# 	self.publish_in_hub = 1
 
 	def after_insert(self):
 		'''set opening stock and item price'''