feat: Sidebar in vue
diff --git a/erpnext/public/js/hub/PageContainer.vue b/erpnext/public/js/hub/PageContainer.vue
index ca4c63a..ae9d460 100644
--- a/erpnext/public/js/hub/PageContainer.vue
+++ b/erpnext/public/js/hub/PageContainer.vue
@@ -12,6 +12,7 @@
 import PublishedProducts from './pages/PublishedProducts.vue';
 import Buying from './pages/Buying.vue';
 import BuyingMessages from './pages/BuyingMessages.vue';
+import NotFound from './pages/NotFound.vue';
 
 const route_map = {
 	'marketplace/home': Home,
@@ -74,6 +75,10 @@
 				}
 			}
 
+			if (!route) {
+				return NotFound;
+			}
+
 			return route_map[route];
 		}
 	}
diff --git a/erpnext/public/js/hub/Sidebar.vue b/erpnext/public/js/hub/Sidebar.vue
new file mode 100644
index 0000000..208d8fe
--- /dev/null
+++ b/erpnext/public/js/hub/Sidebar.vue
@@ -0,0 +1,102 @@
+<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" v-for="category in categories" :key="category.label" v-route="category.route">
+				{{ category.label }}
+			</li>
+		</ul>
+	</div>
+</template>
+<script>
+export default {
+	data() {
+		return {
+			items: [
+				{
+					label: __('Browse'),
+					route: 'marketplace/home'
+				},
+				{
+					label: __('Become a Seller'),
+					action: this.show_register_dialog,
+					condition: () => !hub.settings.registered
+				},
+				{
+					label: __('Saved Products'),
+					route: 'marketplace/saved-products',
+					condition: () => hub.settings.registered
+				},
+				{
+					label: __('Your Profile'),
+					route: 'marketplace/profile',
+					condition: () => hub.settings.registered
+				},
+				{
+					label: __('Your Products'),
+					route: 'marketplace/my-products',
+					condition: () => hub.settings.registered
+				},
+				{
+					label: __('Publish Products'),
+					route: 'marketplace/publish',
+					condition: () => hub.settings.registered
+				},
+				{
+					label: __('Selling'),
+					route: 'marketplace/selling',
+					condition: () => hub.settings.registered
+				},
+				{
+					label: __('Buying'),
+					route: 'marketplace/buying',
+					condition: () => hub.settings.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();
+				});
+			});
+	},
+	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/marketplace.js b/erpnext/public/js/hub/marketplace.js
index 10a55aa..663e803 100644
--- a/erpnext/public/js/hub/marketplace.js
+++ b/erpnext/public/js/hub/marketplace.js
@@ -3,10 +3,9 @@
 
 // pages
 import './pages/item';
-import './pages/messages';
-import './pages/buying_messages';
 
 import PageContainer from './PageContainer.vue';
+import Sidebar from './Sidebar.vue';
 import Home from './pages/Home.vue';
 import SavedProducts from './pages/SavedProducts.vue';
 import Publish from './pages/Publish.vue';
@@ -72,79 +71,10 @@
 	make_sidebar() {
 		this.$sidebar = this.$parent.find('.layout-side-section').addClass('hidden-xs');
 
-		this.make_sidebar_nav_buttons();
-		this.make_sidebar_categories();
-	}
-
-	make_sidebar_nav_buttons() {
-		let $nav_group = this.$sidebar.find('[data-nav-buttons]');
-		if (!$nav_group.length) {
-			$nav_group = $('<ul class="list-unstyled hub-sidebar-group" data-nav-buttons>').appendTo(this.$sidebar);
-		}
-		$nav_group.empty();
-
-		const user_specific_items_html = this.registered
-			? `<li class="hub-sidebar-item" data-route="marketplace/saved-products">
-					${__('Saved Products')}
-				</li>
-				<li class="hub-sidebar-item text-muted" data-route="marketplace/profile">
-					${__('Your Profile')}
-				</li>
-				<li class="hub-sidebar-item text-muted" data-route="marketplace/publish">
-					${__('Publish Products')}
-				</li>
-				<li class="hub-sidebar-item text-muted" data-route="marketplace/selling">
-					${__('Selling')}
-				</li>
-				<li class="hub-sidebar-item text-muted" data-route="marketplace/buying">
-					${__('Buying')}
-				</li>
-				`
-
-			: `<li class="hub-sidebar-item text-muted" data-action="show_register_dialog">
-					${__('Become a seller')}
-				</li>`;
-
-		$nav_group.append(`
-			<li class="hub-sidebar-item" data-route="marketplace/home">
-				${__('Browse')}
-			</li>
-			${user_specific_items_html}
-		`);
-	}
-
-	make_sidebar_categories() {
-		hub.call('get_categories')
-			.then(categories => {
-				categories = categories.map(d => d.name);
-
-				const sidebar_items = [
-					`<li class="hub-sidebar-item bold is-title">
-						${__('Category')}
-					</li>`,
-					`<li class="hub-sidebar-item active" data-route="marketplace/home">
-						${__('All')}
-					</li>`,
-					...(this.registered
-						? [`<li class="hub-sidebar-item active" data-route="marketplace/my-products">
-							${__('Your Products')}
-						</li>`]
-						: []),
-					...categories.map(category => `
-						<li class="hub-sidebar-item text-muted" data-route="marketplace/category/${category}">
-							${__(category)}
-						</li>
-					`)
-				];
-
-				this.$sidebar.append(`
-					<ul class="list-unstyled">
-						${sidebar_items.join('')}
-					</ul>
-				`);
-
-				this.update_sidebar();
-			});
+		new Vue({
+			el: $('<div>').appendTo(this.$sidebar)[0],
+			render: h => h(Sidebar)
+		});
 	}
 
 	make_body() {
@@ -162,18 +92,6 @@
 		});
 	}
 
-	update_sidebar() {
-		const route = frappe.get_route();
-		const route_str = route.join('/');
-		const part_route_str = route.slice(0, 2).join('/');
-		const $sidebar_item = this.$sidebar.find(`[data-route="${route_str}"], [data-route="${part_route_str}"]`);
-
-
-		const $siblings = this.$sidebar.find('[data-route]');
-		$siblings.removeClass('active').addClass('text-muted');
-		$sidebar_item.addClass('active').removeClass('text-muted');
-	}
-
 	refresh() {
 
 	}
diff --git a/erpnext/public/js/hub/vue-plugins.js b/erpnext/public/js/hub/vue-plugins.js
index 5555945..00b29b0 100644
--- a/erpnext/public/js/hub/vue-plugins.js
+++ b/erpnext/public/js/hub/vue-plugins.js
@@ -5,7 +5,9 @@
 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) {
diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less
index 4cbe351..618f98f 100644
--- a/erpnext/public/less/hub.less
+++ b/erpnext/public/less/hub.less
@@ -100,6 +100,7 @@
 	}
 
 	.hub-card-image {
+		position: relative;
 		width: 100%;
 		height: 100%;
 		object-fit: contain;
@@ -212,6 +213,7 @@
 	}
 
 	.hub-list-image {
+		position: relative;
 		width: 58px;
 		height: 58px;
 		border-right: 1px solid @border-color;