From 0a9ea15b5899c275aacb7cf7113e184777ba4abf Mon Sep 17 00:00:00 2001
From: Jonathan Cremin <jonathan@crem.in>
Date: Sun, 23 Oct 2016 23:04:32 +0100
Subject: [PATCH] Add error handling to the frontend

---
 app.js                           |  3 +++
 lib/error-handler.js             | 39 ++++++++++++++++++++++++++++++
 public/src/components/search.vue | 41 ++++++++++++++++++++++----------
 public/src/style/style.css       |  6 ++---
 public/src/views/index.vue       |  2 +-
 routes/index.js                  |  4 ++--
 6 files changed, 77 insertions(+), 18 deletions(-)
 create mode 100644 lib/error-handler.js

diff --git a/app.js b/app.js
index 461218e..ca4e7aa 100644
--- a/app.js
+++ b/app.js
@@ -15,6 +15,7 @@ import recent from './routes/recent';
 import search from './routes/search';
 import share from './routes/share';
 import itunesProxy from './routes/itunes-proxy';
+import errorHandler from './lib/error-handler';
 
 const debug = debuglog('match.audio');
 
@@ -22,6 +23,8 @@ process.env.VUE_ENV = 'server';
 
 const app = koa();
 
+app.use(errorHandler());
+
 app.use(bodyparser());
 app.use(cors());
 app.use(compress({ flush: zlib.Z_SYNC_FLUSH }));
diff --git a/lib/error-handler.js b/lib/error-handler.js
new file mode 100644
index 0000000..d6c8020
--- /dev/null
+++ b/lib/error-handler.js
@@ -0,0 +1,39 @@
+import debuglog from 'debug';
+
+const debug = debuglog('match.audio');
+
+export default function () {
+  return function* error(next) {
+    this.set('Server', 'Nintendo 64');
+    try {
+      yield next;
+    } catch (err) {
+      if (err.status === 404) {
+        this.body = '404 Page Not Found';
+      } else if (err.status >= 400 && err.status < 500) {
+        this.status = err.status;
+        this.body = err.error;
+      } else {
+        debug('Error: %o', err);
+        throw err;
+      }
+    }
+
+    if (this.status !== 404) return;
+
+    switch (this.accepts('html', 'json')) {
+      case 'html':
+        this.type = 'html';
+        this.body = '404 Page Not Found';
+        break;
+      case 'json':
+        this.body = {
+          message: 'Page Not Found',
+        };
+        break;
+      default:
+        this.type = 'text';
+        this.body = 'Page Not Found';
+    }
+  };
+}
diff --git a/public/src/components/search.vue b/public/src/components/search.vue
index 82ced27..3bcded1 100644
--- a/public/src/components/search.vue
+++ b/public/src/components/search.vue
@@ -1,12 +1,18 @@
 <template>
-  <form role="form" method="post" action="/search" v-on:submit="submit">
-    <p class="control has-addons">
-      <input class="input is-expanded is-large" type="text" placeholder="Paste your link here" v-model="url">
-      <button type="submit" class="button is-primary is-large">
-        Share Music
-      </button>
-    </p>
-  </form>
+  <div class="search">
+    <form role="form" method="post" action="/search" v-on:submit="submit">
+      <p class="control has-addons">
+        <input class="input is-expanded is-large" type="text" placeholder="Paste your link here" v-model="url">
+        <button type="submit" class="button is-primary is-large" v-bind:class="{ 'is-loading': submitting }">
+          Share Music
+        </button>
+      </p>
+    </form>
+    <div class="notification is-warning" v-if="error">
+      <button class="delete" v-on:click="error = false"></button>
+      {{ error }}
+    </div>
+  </div>
 </template>
 
 <script>
@@ -16,15 +22,23 @@ export default {
   name: 'search-view',
   data() {
     return {
+      error: null,
+      submitting: false,
       url: '',
     };
   },
   methods: {
     submit (event) {
+      this.submitting = true;
       event.preventDefault();
       musicSearch(this.url).end((req, res) => {
-        const item = res.body;
-        this.$router.push(`/${item.service}/${item.albumName ? 'track' : 'album'}/${item.externalId}`);
+        this.submitting = false;
+        if (res.status == 200) {
+          const item = res.body;
+          this.$router.push(`/${item.service}/${item.albumName ? 'track' : 'album'}/${item.externalId}`);
+        } else {
+          this.error = res.body.message;
+        }
       });
     },
   },
@@ -47,8 +61,11 @@ export default {
 .input:focus {
   border-color: #FE4365;
 }
+.search {
+  margin-bottom: 25vh;
+}
 form {
-  margin-bottom: 50px;
-  margin-top: 200px;
+  margin-top: 25vh;
+  margin-bottom: 20px;
 }
 </style>
diff --git a/public/src/style/style.css b/public/src/style/style.css
index a31f3ed..d7176a7 100644
--- a/public/src/style/style.css
+++ b/public/src/style/style.css
@@ -39,7 +39,7 @@ h1 .lighter {
   margin-bottom: 30px;
 }
 .home {
-  width: 600px;
+  max-width: 600px;
   margin-top: 40px;
 }
 p {
@@ -76,8 +76,8 @@ p {
   border-color: #FE4365;
 }
 form {
-  margin-bottom: 50px;
-  margin-top: 200px;
+  margin-top: 25vh;
+  margin-bottom: 20px;
 }
 
 /* share.vue */
diff --git a/public/src/views/index.vue b/public/src/views/index.vue
index a0172fe..a7bda41 100644
--- a/public/src/views/index.vue
+++ b/public/src/views/index.vue
@@ -103,7 +103,7 @@ export default {
   margin-bottom: 30px;
 }
 .home {
-  width: 600px;
+  max-width: 600px;
   margin-top: 40px;
 }
 p {
diff --git a/routes/index.js b/routes/index.js
index 4e68417..fc06b49 100644
--- a/routes/index.js
+++ b/routes/index.js
@@ -33,10 +33,10 @@ export default function* () {
   const html = yield render(url, initialState);
 
   const head = {
-    title: `Share Music`,
+    title: 'Share Music',
     shareUrl: `${this.request.origin}${url}`,
     image: `${this.request.origin}/assets/images/logo-512.png`,
-  }
+  };
 
   yield this.render('index', {
     initialState,