feat: Add helper directives

- v-route
  automatically route to a valid frappe route
- v-img-src
  handle img loading and error handling
diff --git a/erpnext/public/js/hub/components/ItemCard.vue b/erpnext/public/js/hub/components/ItemCard.vue
index 7975868..f34fddc 100644
--- a/erpnext/public/js/hub/components/ItemCard.vue
+++ b/erpnext/public/js/hub/components/ItemCard.vue
@@ -15,7 +15,7 @@
 				</i>
 			</div>
 			<div class="hub-card-body">
-				<img class="hub-card-image" :src="item.image"/>
+				<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">
diff --git a/erpnext/public/js/hub/components/ItemCardsContainer.vue b/erpnext/public/js/hub/components/ItemCardsContainer.vue
index 5af82de..748c272 100644
--- a/erpnext/public/js/hub/components/ItemCardsContainer.vue
+++ b/erpnext/public/js/hub/components/ItemCardsContainer.vue
@@ -5,8 +5,7 @@
 			:message="empty_state_message"
 			:bordered="true"
 			:height="80"
-		>
-		</empty-state>
+		/>
 		<item-card
 			v-for="item in items"
 			:key="container_name + '_' +item[item_id_fieldname]"
diff --git a/erpnext/public/js/hub/vue-plugins.js b/erpnext/public/js/hub/vue-plugins.js
new file mode 100644
index 0000000..5555945
--- /dev/null
+++ b/erpnext/public/js/hub/vue-plugins.js
@@ -0,0 +1,44 @@
+import Vue from 'vue/dist/vue.js';
+Vue.prototype.__ = window.__;
+Vue.prototype.frappe = window.frappe;
+
+Vue.directive('route', {
+	bind(el, binding) {
+		const route = binding.value;
+		el.classList.add('cursor-pointer');
+		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);
+	}
+});