[refactor][major] Separate out Components
- item_card
- item_card_container
- detail_view
- search_bar
- reviews
- skeleton_state
- empty_state
- empty_state
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..1f0d542
--- /dev/null
+++ b/erpnext/public/js/hub/components/detail_view.js
@@ -0,0 +1,160 @@
+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 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">
+						<button class="btn btn-default text-muted favourite-button" data-action="add_to_favourites">
+							${__('Add to Favourites')} <i class="octicon octicon-heart text-extra-muted"></i>
+						</button>
+						<button class="btn btn-primary" data-action="contact_seller">
+							${__('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">Product 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;
+}
+
+function get_profile_html(profile) {
+	const p = profile;
+	const profile_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">
+			<div class="col-md-3">
+				<div class="hub-item-image">
+					<img src="${p.logo}">
+				</div>
+			</div>
+			<div class="col-md-6">
+				<h2>${p.company}</h2>
+				<div class="text-muted">
+					<p>${p.country}</p>
+					<p>${p.site_name}</p>
+					<p>${__(`Joined ${comment_when(p.creation)}`)}</p>
+				</div>
+				<hr>
+				<div class="hub-item-description">
+				${'description'
+					? `<p>${p.company_description}</p>`
+					: `<p>__('No description')</p`
+				}
+				</div>
+			</div>
+		</div>
+
+	</div>`;
+
+	return profile_html;
+}
+
+export {
+	get_detail_view_html,
+	get_profile_html
+}
diff --git a/erpnext/public/js/hub/components/empty_state.js b/erpnext/public/js/hub/components/empty_state.js
new file mode 100644
index 0000000..0e1ad46
--- /dev/null
+++ b/erpnext/public/js/hub/components/empty_state.js
@@ -0,0 +1,10 @@
+function get_empty_state(message, action) {
+	return `<div class="empty-state flex align-center flex-column justify-center">
+		<p class="text-muted">${message}</p>
+		${action ? `<p>${action}</p>`: ''}
+	</div>`;
+}
+
+export {
+    get_empty_state
+}
diff --git a/erpnext/public/js/hub/helpers.js b/erpnext/public/js/hub/components/item_card.js
similarity index 68%
rename from erpnext/public/js/hub/helpers.js
rename to erpnext/public/js/hub/components/item_card.js
index 7c4c919..733df62 100644
--- a/erpnext/public/js/hub/helpers.js
+++ b/erpnext/public/js/hub/components/item_card.js
@@ -1,27 +1,3 @@
-function get_empty_state(message, action) {
-	return `<div class="empty-state flex align-center flex-column justify-center">
-		<p class="text-muted">${message}</p>
-		${action ? `<p>${action}</p>`: ''}
-	</div>`;
-}
-
-function get_item_card_container_html(items, title='', get_item_html = get_item_card_html, action='') {
-	const items_html = (items || []).map(item => get_item_html(item)).join('');
-	const title_html = title
-		? `<div class="hub-card-container-header col-sm-12 margin-bottom flex">
-				<h4>${title}</h4>
-				${action}
-			</div>`
-		: '';
-
-	const html = `<div class="row hub-card-container">
-		${title_html}
-		${items_html}
-	</div>`;
-
-	return html;
-}
-
 function get_item_card_html(item) {
 	const item_name = item.item_name || item.name;
 	const title = strip_html(item_name);
@@ -117,28 +93,8 @@
 	return rating_html;
 }
 
-function make_search_bar({wrapper, on_search, placeholder = __('Search for anything')}) {
-	const $search = $(`
-		<div class="hub-search-container">
-			<input type="text" class="form-control" placeholder="${placeholder}">
-		</div>`
-	);
-	wrapper.append($search);
-	const $search_input = $search.find('input');
-
-	$search_input.on('keydown', frappe.utils.debounce((e) => {
-		if (e.which === frappe.ui.keyCode.ENTER) {
-			const search_value = $search_input.val();
-			on_search(search_value);
-		}
-	}, 300));
-}
-
 export {
-	get_empty_state,
-	get_item_card_container_html,
 	get_item_card_html,
-	get_local_item_card_html,
-	get_rating_html,
-	make_search_bar,
+    get_local_item_card_html,
+    get_rating_html
 }
diff --git a/erpnext/public/js/hub/components/items_container.js b/erpnext/public/js/hub/components/items_container.js
new file mode 100644
index 0000000..dd29836
--- /dev/null
+++ b/erpnext/public/js/hub/components/items_container.js
@@ -0,0 +1,22 @@
+import { get_item_card_html } from './item_card';
+
+function get_item_card_container_html(items, title='', get_item_html = get_item_card_html, action='') {
+	const items_html = (items || []).map(item => get_item_html(item)).join('');
+	const title_html = title
+		? `<div class="hub-card-container-header col-sm-12 margin-bottom flex">
+				<h4>${title}</h4>
+				${action}
+			</div>`
+		: '';
+
+	const html = `<div class="row hub-card-container">
+		${title_html}
+		${items_html}
+	</div>`;
+
+	return html;
+}
+
+export {
+	get_item_card_container_html
+}
diff --git a/erpnext/public/js/hub/components/reviews.js b/erpnext/public/js/hub/components/reviews.js
new file mode 100644
index 0000000..616f2fb
--- /dev/null
+++ b/erpnext/public/js/hub/components/reviews.js
@@ -0,0 +1,71 @@
+import { get_rating_html } from './item_card';
+
+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>`;
+}
+
+export {
+	get_review_html
+}
diff --git a/erpnext/public/js/hub/components/search_bar.js b/erpnext/public/js/hub/components/search_bar.js
new file mode 100644
index 0000000..9526516
--- /dev/null
+++ b/erpnext/public/js/hub/components/search_bar.js
@@ -0,0 +1,20 @@
+function make_search_bar({wrapper, on_search, placeholder = __('Search for anything')}) {
+	const $search = $(`
+		<div class="hub-search-container">
+			<input type="text" class="form-control" placeholder="${placeholder}">
+		</div>`
+	);
+	wrapper.append($search);
+	const $search_input = $search.find('input');
+
+	$search_input.on('keydown', frappe.utils.debounce((e) => {
+		if (e.which === frappe.ui.keyCode.ENTER) {
+			const search_value = $search_input.val();
+			on_search(search_value);
+		}
+	}, 300));
+}
+
+export {
+    make_search_bar
+}
diff --git a/erpnext/public/js/hub/components/skeleton_state.js b/erpnext/public/js/hub/components/skeleton_state.js
new file mode 100644
index 0000000..7c68802
--- /dev/null
+++ b/erpnext/public/js/hub/components/skeleton_state.js
@@ -0,0 +1,27 @@
+function get_detail_skeleton_html() {
+	const skeleton = `<div class="hub-item-container">
+		<div class="row">
+			<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>`;
+
+	return skeleton;
+}
+
+export {
+	get_detail_skeleton_html
+}
diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js
index 18a1bb0..1543243 100644
--- a/erpnext/public/js/hub/marketplace.js
+++ b/erpnext/public/js/hub/marketplace.js
@@ -13,7 +13,6 @@
 import './pages/not_found';
 
 // helpers
-import './helpers';
 import './hub_call';
 
 frappe.provide('hub');
diff --git a/erpnext/public/js/hub/pages/category.js b/erpnext/public/js/hub/pages/category.js
index 13a0a92..118d196 100644
--- a/erpnext/public/js/hub/pages/category.js
+++ b/erpnext/public/js/hub/pages/category.js
@@ -1,5 +1,5 @@
 import SubPage from './subpage';
-import { get_item_card_container_html } from '../helpers';
+import { get_item_card_container_html } from '../components/items_container';
 
 erpnext.hub.Category = class Category extends SubPage {
 	refresh() {
diff --git a/erpnext/public/js/hub/pages/favourites.js b/erpnext/public/js/hub/pages/favourites.js
index 704caea..d4a8cb3 100644
--- a/erpnext/public/js/hub/pages/favourites.js
+++ b/erpnext/public/js/hub/pages/favourites.js
@@ -1,5 +1,5 @@
 import SubPage from './subpage';
-import { get_item_card_container_html } from '../helpers';
+import { get_item_card_container_html } from '../components/items_container';
 
 erpnext.hub.Favourites = class Favourites extends SubPage {
 	refresh() {
@@ -10,7 +10,9 @@
 	}
 
 	get_favourites() {
-		return hub.call('get_item_favourites');
+		return hub.call('get_favourite_items_of_seller', {
+			hub_seller: hub.settings.company_email
+		});
 	}
 
 	render(items) {
@@ -18,4 +20,4 @@
 		const html = get_item_card_container_html(items, __('Favourites'));
 		this.$wrapper.append(html)
 	}
-}
\ No newline at end of file
+}
diff --git a/erpnext/public/js/hub/pages/home.js b/erpnext/public/js/hub/pages/home.js
index eefe3b8..b7a55ce 100644
--- a/erpnext/public/js/hub/pages/home.js
+++ b/erpnext/public/js/hub/pages/home.js
@@ -1,5 +1,7 @@
 import SubPage from './subpage';
-import { make_search_bar, get_item_card_container_html, get_item_card_html } from '../helpers';
+import { make_search_bar } from '../components/search_bar';
+import { get_item_card_container_html } from '../components/items_container';
+import { get_item_card_html } from '../components/item_card';
 
 erpnext.hub.Home = class Home extends SubPage {
 	make_wrapper() {
diff --git a/erpnext/public/js/hub/pages/item.js b/erpnext/public/js/hub/pages/item.js
index ec3d6ed..207c94c 100644
--- a/erpnext/public/js/hub/pages/item.js
+++ b/erpnext/public/js/hub/pages/item.js
@@ -1,5 +1,7 @@
 import SubPage from './subpage';
-import { get_rating_html } from '../helpers';
+import { get_detail_view_html } from '../components/detail_view';
+import { get_detail_skeleton_html } from '../components/skeleton_state';
+import { get_review_html } from '../components/reviews';
 
 erpnext.hub.Item = class Item extends SubPage {
 	refresh() {
@@ -16,30 +18,12 @@
 			});
 	}
 
-	show_skeleton() {
-		const skeleton = `<div class="hub-item-container">
-			<div class="row">
-				<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>`;
 
-		this.$wrapper.html(skeleton);
+	show_skeleton() {
+		this.$wrapper.html(get_detail_skeleton_html());
 	}
 
+
 	get_item(hub_item_code) {
 		return hub.call('get_item_details', {
 			hub_seller: hub.settings.company_email,
@@ -47,123 +31,9 @@
 		});
 	}
 
+
 	render(item) {
-		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 menu_items = '';
-
-		if(this.own_item) {
-			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">
-							<button class="btn btn-default text-muted" data-action="add_to_favourites">
-								${__('Add to Favourites')} <i class="octicon octicon-heart text-extra-muted"></i>
-							</button>
-							<button class="btn btn-primary" data-action="contact_seller">
-								${__('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">Product 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>
-		`;
-
+		const html = get_detail_view_html(item, this.own_item);
 		this.$wrapper.html(html);
 
 		this.make_review_area();
@@ -171,10 +41,11 @@
 		this.get_reviews()
 			.then(reviews => {
 				this.reviews = reviews;
-				this.render_reviews(reviews);
+				this.render_reviews();
 			});
 	}
 
+
 	edit_details() {
 		if (!this.edit_dialog) {
 			this.edit_dialog = new frappe.ui.Dialog({
@@ -185,6 +56,7 @@
 		this.edit_dialog.show();
 	}
 
+
 	unpublish_item() {
 		if(!this.unpublish_dialog) {
 			this.unpublish_dialog = new frappe.ui.Dialog({
@@ -196,6 +68,16 @@
 		this.unpublish_dialog.show();
 	}
 
+
+	add_to_favourites(favourite_button) {
+		$(favourite_button).html('Added to Favourites').addClass('disabled');
+		return hub.call('remove_item_from_seller_favourites', {
+			hub_item_code: this.hub_item_code,
+			hub_seller: hub.settings.company_email
+		});
+	}
+
+
 	contact_seller() {
 		const d = new frappe.ui.Dialog({
 			title: __('Send a message'),
@@ -220,37 +102,48 @@
 		d.show();
 	}
 
+
 	make_review_area() {
 		this.comment_area = new frappe.ui.ReviewArea({
 			parent: this.$wrapper.find('.timeline-head').empty(),
 			mentions: [],
-			on_submit: (values) => {
-				values.user = frappe.session.user;
-				values.username = frappe.session.user_fullname;
-
-				hub.call('add_item_review', {
-					hub_item_code: this.hub_item_code,
-					review: JSON.stringify(values)
-				})
-				.then(review => {
-					this.reviews = this.reviews || [];
-					this.reviews.push(review);
-					this.render_reviews(this.reviews);
-
-					this.comment_area.reset();
-				});
-			}
+			on_submit: this.on_submit_review.bind(this)
 		});
 	}
 
+
+	on_submit_review(values) {
+		values.user = frappe.session.user;
+		values.username = frappe.session.user_fullname;
+
+		hub.call('add_item_review', {
+			hub_item_code: this.hub_item_code,
+			review: JSON.stringify(values)
+		})
+		.then(this.push_review_in_review_area.bind(this));
+	}
+
+
+	push_review_in_review_area(review) {
+		this.reviews = this.reviews || [];
+		this.reviews.push(review);
+		this.render_reviews();
+
+		this.comment_area.reset();
+	}
+
+
 	get_reviews() {
 		return hub.call('get_item_reviews', { hub_item_code: this.hub_item_code }).catch(() => {});
 	}
 
-	render_reviews(reviews=[]) {
-		this.$wrapper.find('.timeline-items').empty();
 
-		reviews.sort((a, b) => {
+	render_reviews() {
+		const $timeline = this.$wrapper.find('.timeline-items');
+
+		$timeline.empty();
+
+		this.reviews.sort((a, b) => {
 			if (a.modified > b.modified) {
 				return -1;
 			}
@@ -262,75 +155,8 @@
 			return 0;
 		});
 
-		reviews.forEach(review => this.render_review(review));
-	}
-
-	render_review(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);
-
-		const $timeline_items = this.$wrapper.find('.timeline-items');
-
-		$(this.get_timeline_item(review, image_html, edit_html, rating_html))
-			.appendTo($timeline_items);
-	}
-
-	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>`;
+		this.reviews.forEach(review => {
+			$(get_review_html(review)).appendTo($timeline);
+		});
 	}
 }
diff --git a/erpnext/public/js/hub/pages/messages.js b/erpnext/public/js/hub/pages/messages.js
index 7bc99a72..8531e0b 100644
--- a/erpnext/public/js/hub/pages/messages.js
+++ b/erpnext/public/js/hub/pages/messages.js
@@ -1,5 +1,5 @@
 import SubPage from './subpage';
-import { make_search_bar } from '../helpers';
+import { make_search_bar } from '../components/search_bar';
 
 erpnext.hub.Messages = class Messages extends SubPage {
     make_wrapper() {
@@ -115,4 +115,4 @@
             <p>${message.content}</p>
         </div>
     `;
-}
\ No newline at end of file
+}
diff --git a/erpnext/public/js/hub/pages/not_found.js b/erpnext/public/js/hub/pages/not_found.js
index 3b86446..f7ccc2f 100644
--- a/erpnext/public/js/hub/pages/not_found.js
+++ b/erpnext/public/js/hub/pages/not_found.js
@@ -1,4 +1,5 @@
 import SubPage from './subpage';
+import { get_empty_state } from '../components/empty_state';
 
 erpnext.hub.NotFound = class NotFound extends SubPage {
 	refresh() {
diff --git a/erpnext/public/js/hub/pages/publish.js b/erpnext/public/js/hub/pages/publish.js
index 859782e..a76f946 100644
--- a/erpnext/public/js/hub/pages/publish.js
+++ b/erpnext/public/js/hub/pages/publish.js
@@ -1,5 +1,7 @@
 import SubPage from './subpage';
-import { make_search_bar, get_item_card_container_html, get_local_item_card_html } from '../helpers';
+import { get_item_card_container_html } from '../components/items_container';
+import { get_local_item_card_html } from '../components/item_card';
+import { make_search_bar } from '../components/search_bar';
 
 erpnext.hub.Publish = class Publish extends SubPage {
 	make_wrapper() {
diff --git a/erpnext/public/js/hub/pages/published_products.js b/erpnext/public/js/hub/pages/published_products.js
index 1b19a51..f20fb27 100644
--- a/erpnext/public/js/hub/pages/published_products.js
+++ b/erpnext/public/js/hub/pages/published_products.js
@@ -1,5 +1,5 @@
 import SubPage from './subpage';
-import { get_item_card_container_html } from '../helpers';
+import { get_item_card_container_html } from '../components/items_container';
 
 erpnext.hub.PublishedProducts = class PublishedProducts extends SubPage {
 	get_items_and_render() {
diff --git a/erpnext/public/js/hub/pages/register.js b/erpnext/public/js/hub/pages/register.js
index b95ec04..9b07f29 100644
--- a/erpnext/public/js/hub/pages/register.js
+++ b/erpnext/public/js/hub/pages/register.js
@@ -82,29 +82,33 @@
 		this.$form_container.find('.form-message').removeClass('hidden small').addClass('h4').text(__('Become a Seller'))
 
 		this.$form_container.on('click', '.btn-register', (e) => {
-			const form_values = this.field_group.get_values();
+			this.register_seller();
+		});
+	}
 
-			let values_filled = true;
-			const mandatory_fields = ['company', 'company_email', 'company_description'];
-			mandatory_fields.forEach(field => {
-				const value = form_values[field];
-				if (!value) {
-					this.field_group.set_df_property(field, 'reqd', 1);
-					values_filled = false;
-				}
-			});
-			if (!values_filled) return;
+	register_seller() {
+		const form_values = this.field_group.get_values();
 
-			frappe.call({
-				method: 'erpnext.hub_node.doctype.hub_settings.hub_settings.register_seller',
-				args: form_values,
-				btn: $(e.currentTarget)
-			}).then(() => {
-				frappe.set_route('marketplace', 'publish');
+		let values_filled = true;
+		const mandatory_fields = ['company', 'company_email', 'company_description'];
+		mandatory_fields.forEach(field => {
+			const value = form_values[field];
+			if (!value) {
+				this.field_group.set_df_property(field, 'reqd', 1);
+				values_filled = false;
+			}
+		});
+		if (!values_filled) return;
 
-				// custom jquery event
-				this.$wrapper.trigger('seller-registered');
-			});
+		frappe.call({
+			method: 'erpnext.hub_node.doctype.hub_settings.hub_settings.register_seller',
+			args: form_values,
+			btn: $(e.currentTarget)
+		}).then(() => {
+			frappe.set_route('marketplace', 'publish');
+
+			// custom jquery event
+			this.$wrapper.trigger('seller-registered');
 		});
 	}
 }
diff --git a/erpnext/public/js/hub/pages/search.js b/erpnext/public/js/hub/pages/search.js
index 33c2b78..f3dd6fb 100644
--- a/erpnext/public/js/hub/pages/search.js
+++ b/erpnext/public/js/hub/pages/search.js
@@ -1,5 +1,6 @@
 import SubPage from './subpage';
-import { make_search_bar, get_item_card_container_html } from '../helpers';
+import { make_search_bar } from '../components/search_bar';
+import { get_item_card_container_html } from '../components/items_container';
 
 erpnext.hub.SearchPage = class SearchPage extends SubPage {
 	make_wrapper() {
diff --git a/erpnext/public/js/hub/pages/seller.js b/erpnext/public/js/hub/pages/seller.js
index 27b3924..b86e46e 100644
--- a/erpnext/public/js/hub/pages/seller.js
+++ b/erpnext/public/js/hub/pages/seller.js
@@ -1,5 +1,7 @@
 import SubPage from './subpage';
-import { get_item_card_container_html } from '../helpers';
+import { get_profile_html } from '../components/detail_view';
+import { get_item_card_container_html } from '../components/items_container';
+import { get_detail_skeleton_html } from '../components/skeleton_state';
 
 erpnext.hub.Seller = class Seller extends SubPage {
 	make_wrapper() {
@@ -18,64 +20,11 @@
 	}
 
 	show_skeleton() {
-		const skeleton = `<div class="hub-item-container">
-			<div class="row">
-				<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>`;
-
-		this.$wrapper.html(skeleton);
+		this.$wrapper.html(get_detail_skeleton_html());
 	}
 
 	render(data) {
-		const p = data.profile;
-
-		const profile_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">
-				<div class="col-md-3">
-					<div class="hub-item-image">
-						<img src="${p.logo}">
-					</div>
-				</div>
-				<div class="col-md-6">
-					<h2>${p.company}</h2>
-					<div class="text-muted">
-						<p>${p.country}</p>
-						<p>${p.site_name}</p>
-						<p>${__(`Joined ${comment_when(p.creation)}`)}</p>
-					</div>
-					<hr>
-					<div class="hub-item-description">
-					${'description'
-						? `<p>${p.company_description}</p>`
-						: `<p>__('No description')</p`
-					}
-					</div>
-				</div>
-			</div>
-
-		</div>`;
-
-		this.$wrapper.html(profile_html);
+		this.$wrapper.html(get_profile_html(data.profile));
 
 		let html = get_item_card_container_html(data.items, __('Products by ' + p.company));
 		this.$wrapper.append(html);
diff --git a/erpnext/public/js/hub/pages/subpage.js b/erpnext/public/js/hub/pages/subpage.js
index a030e7e..7c75b13 100644
--- a/erpnext/public/js/hub/pages/subpage.js
+++ b/erpnext/public/js/hub/pages/subpage.js
@@ -5,11 +5,11 @@
 
 		// generic action handler
 		this.$wrapper.on('click', '[data-action]', e => {
-			const $this = $(e.currentTarget);
-			const action = $this.data().action;
+			const $target = $(e.currentTarget);
+			const action = $target.data().action;
 
 			if (action && this[action]) {
-				this[action].apply(this);
+				this[action].apply(this, $target);
 			}
 		})
 
@@ -42,4 +42,4 @@
 	hide() {
 		this.$wrapper.hide();
 	}
-}
\ No newline at end of file
+}
diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less
index d8ac9dd..7ac1684 100644
--- a/erpnext/public/less/hub.less
+++ b/erpnext/public/less/hub.less
@@ -25,15 +25,15 @@
 		font-size: @text-medium;
 	}
 
-	.btn-primary {
-		background-color: #89da28;
-		border-color: #61ca23;
-	}
+	// .btn-primary {
+	// 	background-color: #89da28;
+	// 	border-color: #61ca23;
+	// }
 
-	.btn-primary:hover {
-		background-color: #61ca23;
-		border-color: #59b81c;
-	}
+	// .btn-primary:hover {
+	// 	background-color: #61ca23;
+	// 	border-color: #59b81c;
+	// }
 
 	.progress-bar {
 		background-color: #89da28;