Isomorphic marketplace (#15384)

* feat: Make marketplace run on hub server in Guest mode

* fix: Skip page actions on server

* fix: Limit PageContainer to marketplace route

* fix(eslint): Add hub variable to globals
diff --git a/.eslintrc b/.eslintrc
index c9a1b03..20cf293 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -51,8 +51,7 @@
 	"globals": {
 		"frappe": true,
 		"erpnext": true,
-		"schools": true,
-		"education": true,
+		"hub": true,
 
 		"$": true,
 		"jQuery": true,
diff --git a/erpnext/public/build.json b/erpnext/public/build.json
index e62ae59..c34eef2 100644
--- a/erpnext/public/build.json
+++ b/erpnext/public/build.json
@@ -3,6 +3,9 @@
         "public/less/erpnext.less",
         "public/less/hub.less"
     ],
+    "css/marketplace.css": [
+        "public/less/hub.less"
+    ],
     "js/erpnext-web.min.js": [
         "public/js/website_utils.js",
         "public/js/shopping_cart.js"
diff --git a/erpnext/public/js/hub/PageContainer.vue b/erpnext/public/js/hub/PageContainer.vue
index 0bc6712..5ec92d5 100644
--- a/erpnext/public/js/hub/PageContainer.vue
+++ b/erpnext/public/js/hub/PageContainer.vue
@@ -53,8 +53,10 @@
 	},
 	mounted() {
 		frappe.route.on('change', () => {
-			this.set_current_page();
-			frappe.utils.scroll_to(0);
+			if (frappe.get_route()[0] === 'marketplace') {
+				this.set_current_page();
+				frappe.utils.scroll_to(0);
+			}
 		});
 	},
 	methods: {
diff --git a/erpnext/public/js/hub/hub_call.js b/erpnext/public/js/hub/hub_call.js
index a8bfa2e..5545a49 100644
--- a/erpnext/public/js/hub/hub_call.js
+++ b/erpnext/public/js/hub/hub_call.js
@@ -22,13 +22,23 @@
 			});
 		}
 
-		frappe.call({
-			method: 'erpnext.hub_node.api.call_hub_method',
-			args: {
-				method,
-				params: args
-			}
-		}).then(r => {
+		let res;
+		if (hub.is_server) {
+			res = frappe.call({
+				method: 'hub.hub.api.' + method,
+				args
+			});
+		} else {
+			res = frappe.call({
+				method: 'erpnext.hub_node.api.call_hub_method',
+				args: {
+					method,
+					params: args
+				}
+			});
+		}
+
+		res.then(r => {
 			if (r.message) {
 				const response = r.message;
 				if (response.error) {
diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js
index 222326f..7ef87c4 100644
--- a/erpnext/public/js/hub/marketplace.js
+++ b/erpnext/public/js/hub/marketplace.js
@@ -12,8 +12,10 @@
 
 frappe.provide('hub');
 frappe.provide('erpnext.hub');
+frappe.provide('frappe.route');
 
 $.extend(erpnext.hub, EventEmitter.prototype);
+$.extend(frappe.route, EventEmitter.prototype);
 
 erpnext.hub.Marketplace = class Marketplace {
 	constructor({ parent }) {
@@ -28,15 +30,18 @@
 			this.setup_events();
 			this.refresh();
 
-			if (!hub.is_seller_registered()) {
-				this.page.set_primary_action('Become a Seller', this.show_register_dialog.bind(this))
-			} else {
-				this.page.set_secondary_action('Add Users', this.show_add_user_dialog.bind(this));
+			if (!hub.is_server) {
+				if (!hub.is_seller_registered()) {
+					this.page.set_primary_action('Become a Seller', this.show_register_dialog.bind(this))
+				} else {
+					this.page.set_secondary_action('Add Users', this.show_add_user_dialog.bind(this));
+				}
 			}
 		});
 	}
 
 	setup_header() {
+		if (hub.is_server) return;
 		this.page.set_title(__('Marketplace'));
 	}
 
@@ -76,9 +81,11 @@
 			render: h => h(PageContainer)
 		});
 
-		erpnext.hub.on('seller-registered', () => {
-			this.page.clear_primary_action();
-		});
+		if (!hub.is_server) {
+			erpnext.hub.on('seller-registered', () => {
+				this.page.clear_primary_action();
+			});
+		}
 	}
 
 	refresh() {
@@ -182,7 +189,7 @@
 	}
 
 	update_hub_settings() {
-		return frappe.db.get_doc('Marketplace Settings').then(doc => {
+		return hub.get_settings().then(doc => {
 			hub.settings = doc;
 		});
 	}
@@ -198,6 +205,15 @@
 			.filter(hub_user => hub_user.user === frappe.session.user)
 			.length === 1;
 	},
+
+	get_settings() {
+		if (frappe.session.user === 'Guest') {
+			return Promise.resolve({
+				registered: 0
+			});
+		}
+		return frappe.db.get_doc('Marketplace Settings');
+	}
 });
 
 /**
diff --git a/erpnext/public/js/hub/pages/Home.vue b/erpnext/public/js/hub/pages/Home.vue
index 9cf0bf4..3536569 100644
--- a/erpnext/public/js/hub/pages/Home.vue
+++ b/erpnext/public/js/hub/pages/Home.vue
@@ -60,9 +60,9 @@
 	},
 	methods: {
 		get_items() {
-			hub.call('get_data_for_homepage', {
+			hub.call('get_data_for_homepage', frappe.defaults ? {
 				country: frappe.defaults.get_user_default('country')
-			})
+			} : null)
 			.then((data) => {
 				this.show_skeleton = false;
 
diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less
index 089915d..8cb7a9c 100644
--- a/erpnext/public/less/hub.less
+++ b/erpnext/public/less/hub.less
@@ -1,7 +1,7 @@
 @import "variables.less";
 @import (reference) "desk.less";
 
-body[data-route^="marketplace/"] {
+body[data-route*="marketplace"] {
 	.layout-side-section {
 		padding-top: 25px;
 		padding-left: 5px;