Merge branch 'hub-redesign' of https://github.com/frappe/erpnext into hub-redesign
diff --git a/erpnext/public/js/hub/components/detail_view.js b/erpnext/public/js/hub/components/detail_view.js
index 9ef48d4..cd73405 100644
--- a/erpnext/public/js/hub/components/detail_view.js
+++ b/erpnext/public/js/hub/components/detail_view.js
@@ -33,6 +33,12 @@
 			${__('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) {
@@ -69,9 +75,7 @@
 
 					<div class="page-actions detail-page-actions">
 						${favourite_button}
-						<button class="btn btn-primary" data-action="contact_seller">
-							${__('Contact Seller')}
-						</button>
+						${contact_seller_button}
 					</div>
 				</div>
 				<div class="col-md-1">
diff --git a/erpnext/public/js/hub/components/item_card.js b/erpnext/public/js/hub/components/item_card.js
index f7f5975..9f5a6ef 100644
--- a/erpnext/public/js/hub/components/item_card.js
+++ b/erpnext/public/js/hub/components/item_card.js
@@ -15,11 +15,16 @@
 	let dot_spacer = '<span aria-hidden="true"> · </span>';
 	subtitle = subtitle.join(dot_spacer);
 
+	// route
+	if (!item.route) {
+		item.route = `marketplace/item/${item.hub_item_code}`
+	}
+
 	const item_html = `
 		<div class="col-md-3 col-sm-4 col-xs-6">
 			<div class="hub-card"
 				data-hub-item-code="${item.hub_item_code}"
-				data-route="marketplace/item/${item.hub_item_code}">
+				data-route="${item.route}">
 
 				<div class="hub-card-header level">
 					<div class="ellipsis">
diff --git a/erpnext/public/js/hub/hub_call.js b/erpnext/public/js/hub/hub_call.js
index d83aeeb..e461564 100644
--- a/erpnext/public/js/hub/hub_call.js
+++ b/erpnext/public/js/hub/hub_call.js
@@ -2,7 +2,7 @@
 frappe.provide('erpnext.hub');
 
 erpnext.hub.cache = {};
-hub.call = function call_hub_method(method, args={}, setup_cache_invalidation = invalidate_after_5_mins) {
+hub.call = function call_hub_method(method, args={}, clear_cache_on_event) {
 	return new Promise((resolve, reject) => {
 
 		// cache
@@ -13,7 +13,14 @@
 
 		// cache invalidation
 		const clear_cache = () => delete erpnext.hub.cache[key];
-		setup_cache_invalidation(clear_cache);
+
+		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',
diff --git a/erpnext/public/js/hub/pages/favourites.js b/erpnext/public/js/hub/pages/favourites.js
index 566f5b0..ae08a68 100644
--- a/erpnext/public/js/hub/pages/favourites.js
+++ b/erpnext/public/js/hub/pages/favourites.js
@@ -26,11 +26,9 @@
 	}
 
 	get_favourites() {
-		return hub.call(
-			'get_favourite_items_of_seller',
-			{ hub_seller: hub.settings.company_email },
-			clear_cache => erpnext.hub.on('action:item_favourite', clear_cache)
-		);
+		return hub.call('get_favourite_items_of_seller', {
+			hub_seller: hub.settings.company_email
+		}, 'action:item_favourite');
 	}
 
 	render(items) {
diff --git a/erpnext/public/js/hub/pages/item.js b/erpnext/public/js/hub/pages/item.js
index a94d584..71a4b0f 100644
--- a/erpnext/public/js/hub/pages/item.js
+++ b/erpnext/public/js/hub/pages/item.js
@@ -105,6 +105,18 @@
 			],
 			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.hub_item_code,
+                    message
+				})
+				.then(() => {
+					d.hide();
+					frappe.set_route('marketplace', 'messages');
+					erpnext.hub.trigger('action:send_message')
+				});
 			}
 		});
 
diff --git a/erpnext/public/js/hub/pages/messages.js b/erpnext/public/js/hub/pages/messages.js
index 8531e0b..a1f734f 100644
--- a/erpnext/public/js/hub/pages/messages.js
+++ b/erpnext/public/js/hub/pages/messages.js
@@ -1,118 +1,105 @@
 import SubPage from './subpage';
-import { make_search_bar } from '../components/search_bar';
+import { get_item_card_container_html } from '../components/items_container';
+import { get_empty_state } from '../components/empty_state';
 
 erpnext.hub.Messages = class Messages extends SubPage {
-    make_wrapper() {
-        super.make_wrapper();
+	make_wrapper() {
+		super.make_wrapper();
+	}
 
-        const html = `
-            <div class="row">
-                <div class="col-md-5">
-                    <div class="seller-list"></div>
-                </div>
-                <div class="col-md-7">
-                    ${get_message_area_html()}
-                </div>
-            </div>
-        `;
+	refresh() {
+		const res = Promise.all([
+			this.get_buying_items(),
+			this.get_selling_items()
+		]);
 
-        make_search_bar({
-            wrapper: this.$wrapper,
-            on_search: keyword => {
+		res.then(([buying_items, selling_items]) => {
+			this.empty();
 
-            },
-            placeholder: __('Search for messages')
-        })
+			if (buying_items.length) {
+				buying_items.map(item => {
+					item.route = `marketplace/buy/${item.hub_item_code}`
+				})
+				this.render(buying_items, __('Buying'));
+			}
 
-        this.$wrapper.append(html);
+			if (selling_items.length) {
+				// selling_items.map(item => {
+				// 	item.route = `marketplace/sell/${item.hub_item_code}/${}`
+				// });
+				this.render(selling_items, __('Selling'));
+			}
 
-        this.message_input = new frappe.ui.CommentArea({
-            parent: this.$wrapper.find('.message-input'),
-            on_submit: (message) => {
-                this.message_input.reset();
+			if (!buying_items.length && !selling_items.length) {
+				this.render_empty_state();
+			}
+		});
+	}
 
-                // append message html
-                const $message_list = this.$wrapper.find('.message-list');
-                const message_html = get_message_html({
-                    sender: hub.settings.company_email,
-                    content: message
-                });
-                $message_list.append(message_html);
-                frappe.dom.scroll_to_bottom($message_list);
+	render(items = [], title) {
+		const html = get_item_card_container_html(items, title);
+		this.$wrapper.append(html);
+	}
 
-                const to_seller = frappe.get_route()[2];
-                hub.call('send_message', {
-                    from_seller: hub.settings.company_email,
-                    to_seller,
-                    message
-                });
-            },
-            no_wrapper: true
-        });
-    }
+	render_empty_state() {
+		const empty_state = get_empty_state(__('You haven\'t interacted with any seller yet.'));
+		this.$wrapper.html(empty_state);
+	}
 
-    refresh() {
-        this.get_interactions()
-            .then(sellers => {
-                const html = sellers.map(get_list_item_html).join('');
-                this.$wrapper.find('.seller-list').html(html);
-            });
+	get_buying_items() {
+		return hub.call('get_buying_items_for_messages', {}, 'action:send_message');
+	}
 
-        this.get_messages()
-            .then(messages => {
-                const $message_list = this.$wrapper.find('.message-list');
-                const html = messages.map(get_message_html).join('');
-                $message_list.html(html);
-                frappe.dom.scroll_to_bottom($message_list);
-            });
-    }
+	get_selling_items() {
+		return hub.call('get_selling_items_for_messages');
+	}
 
-    get_interactions() {
-        return hub.call('get_sellers_with_interactions', { for_seller: hub.settings.company_email });
-    }
+	get_interactions() {
+		return hub.call('get_sellers_with_interactions', { for_seller: hub.settings.company_email });
+	}
 
-    get_messages() {
-        const against_seller = frappe.get_route()[2];
-        if (!against_seller) return Promise.resolve([]);
+	get_messages() {
+		const against_seller = frappe.get_route()[2];
+		if (!against_seller) return Promise.resolve([]);
 
-        return hub.call('get_messages', {
-            for_seller: hub.settings.company_email,
-            against_seller: against_seller
-        });
-    }
+		return hub.call('get_messages', {
+			for_seller: hub.settings.company_email,
+			against_seller: against_seller
+		});
+	}
 }
 
 function get_message_area_html() {
-    return `
-        <div class="message-area border padding flex flex-column">
-            <div class="message-list">
-            </div>
-            <div class="message-input">
-            </div>
-        </div>
-    `;
+	return `
+		<div class="message-area border padding flex flex-column">
+			<div class="message-list">
+			</div>
+			<div class="message-input">
+			</div>
+		</div>
+	`;
 }
 
 function get_list_item_html(seller) {
-    const active_class = frappe.get_route()[2] === seller.email ? 'active' : '';
+	const active_class = frappe.get_route()[2] === seller.email ? 'active' : '';
 
-    return `
-        <div class="message-list-item ${active_class}" data-route="marketplace/messages/${seller.email}">
-            <div class="list-item-left">
-                <img src="${seller.image || 'https://picsum.photos/200?random'}">
-            </div>
-            <div class="list-item-body">
-                ${seller.company}
-            </div>
-        </div>
-    `;
+	return `
+		<div class="message-list-item ${active_class}" data-route="marketplace/messages/${seller.email}">
+			<div class="list-item-left">
+				<img src="${seller.image || 'https://picsum.photos/200?random'}">
+			</div>
+			<div class="list-item-body">
+				${seller.company}
+			</div>
+		</div>
+	`;
 }
 
 function get_message_html(message) {
-    return `
-        <div>
-            <h5>${message.sender}</h5>
-            <p>${message.content}</p>
-        </div>
-    `;
+	return `
+		<div>
+			<h5>${message.sender}</h5>
+			<p>${message.content}</p>
+		</div>
+	`;
 }