diff --git a/cypress/integration/common/steps.js b/cypress/integration/common/steps.js
index 5185c09f9..36dbb50d4 100644
--- a/cypress/integration/common/steps.js
+++ b/cypress/integration/common/steps.js
@@ -311,7 +311,7 @@ Then(
cy.visit(route, {
failOnStatusCode: false
});
- cy.get(".error").should("contain", message);
+ cy.get(".error-message").should("contain", message);
}
);
diff --git a/webapp/layouts/error.spec.js b/webapp/layouts/error.spec.js
new file mode 100644
index 000000000..95234ec5f
--- /dev/null
+++ b/webapp/layouts/error.spec.js
@@ -0,0 +1,47 @@
+import { config, shallowMount } from '@vue/test-utils'
+import Error from './error.vue'
+
+const localVue = global.localVue
+
+config.stubs['nuxt-link'] = ''
+
+describe('error.vue', () => {
+ let mocks, wrapper
+
+ beforeEach(() => {
+ mocks = {
+ $t: jest.fn(key => key),
+ }
+ })
+
+ const Wrapper = (propsData = {}) => {
+ return shallowMount(Error, { mocks, propsData, localVue })
+ }
+
+ describe('shallowMount', () => {
+ it('renders default error message', () => {
+ wrapper = Wrapper({ error: {} })
+ expect(wrapper.find('.error-message').text()).toBe('error-pages.default')
+ })
+
+ it('renders error message to given statusCode', () => {
+ wrapper = Wrapper({ error: { statusCode: 404 } })
+ expect(wrapper.find('.error-message').text()).toBe('error-pages.404-default')
+ })
+
+ it('renders error message to given custom key', () => {
+ wrapper = Wrapper({ error: { statusCode: 404, key: 'my-custom-key' } })
+ expect(wrapper.find('.error-message').text()).toBe('my-custom-key')
+ })
+
+ it('has a link to index page', () => {
+ wrapper = Wrapper({ error: {} })
+ expect(wrapper.find('span[to="/"]').text()).toBe('error-pages.back-to-index')
+ })
+
+ it('has an image related to the status code', () => {
+ wrapper = Wrapper({ error: { statusCode: 404 } })
+ expect(wrapper.find('.error-image').attributes('src')).toBe('/img/svg/errors/error404.svg')
+ })
+ })
+})
diff --git a/webapp/layouts/error.vue b/webapp/layouts/error.vue
new file mode 100644
index 000000000..9143a7ff1
--- /dev/null
+++ b/webapp/layouts/error.vue
@@ -0,0 +1,47 @@
+
+
+
![]()
+
+
{{ $t(message) }}
+
+
{{ $t('error-pages.back-to-index') }}
+
+
+
+
+
diff --git a/webapp/locales/de.json b/webapp/locales/de.json
index d552d51ba..e3e214d32 100644
--- a/webapp/locales/de.json
+++ b/webapp/locales/de.json
@@ -821,5 +821,16 @@
"donations-for": "Spenden für",
"donate-now": "Jetzt spenden",
"amount-of-total": "{amount} von {total} € erreicht"
+ },
+ "error-pages" : {
+ "profile-not-found": "Dieses Profil konnte nicht gefunden werden",
+ "back-to-index": "Zurück zur Startseite",
+ "post-not-found": "Dieser Beitrag konnte nicht gefunden werden",
+ "cannot-edit-post": "Dieser Beitrag kann nicht editiert werden",
+ "403-default": "Kein Zugang zu dieser Seite",
+ "404-default": "Diese Seite konnte nicht gefunden werden",
+ "500-default": "Internal Server Error",
+ "503-default": "Dienst steht nicht zur Verfügung",
+ "default": "Ein Fehler ist aufgetreten"
}
}
diff --git a/webapp/locales/en.json b/webapp/locales/en.json
index d15614ecc..5beec4328 100644
--- a/webapp/locales/en.json
+++ b/webapp/locales/en.json
@@ -821,6 +821,17 @@
"title": "In addition, we regularly hold events where you can also share your impressions and ask questions. You can find a current overview here:",
"description": " https://human-connection.org/events/ "
}
+ },
+ "error-pages" : {
+ "profile-not-found": "This profile could not be found",
+ "back-to-index": "Back to index page",
+ "post-not-found": "This post could not be found",
+ "cannot-edit-post": "This post cannot be edited",
+ "404-default": "This page could not be found",
+ "403-default": "Not authorized to this page",
+ "500-default": "Internal Server Error",
+ "503-default": "Service Unavailable",
+ "default": "An error occurred"
}
}
diff --git a/webapp/middleware/isAdmin.js b/webapp/middleware/isAdmin.js
index 4db10bbb6..12b6c5bac 100644
--- a/webapp/middleware/isAdmin.js
+++ b/webapp/middleware/isAdmin.js
@@ -1,5 +1,5 @@
export default ({ store, error }) => {
if (!store.getters['auth/isAdmin']) {
- return error({ statusCode: 403 })
+ return error({ statusCode: 403, message: 'error-pages.not-authorized' })
}
}
diff --git a/webapp/middleware/isModerator.js b/webapp/middleware/isModerator.js
index e99793a3e..9b17badea 100644
--- a/webapp/middleware/isModerator.js
+++ b/webapp/middleware/isModerator.js
@@ -1,5 +1,5 @@
export default ({ store, error }) => {
if (!store.getters['auth/isModerator']) {
- return error({ statusCode: 403 })
+ return error({ statusCode: 403, message: 'error-pages.not-authorized' })
}
}
diff --git a/webapp/mixins/persistentLinks.js b/webapp/mixins/persistentLinks.js
index efc4392e2..ce41251f7 100644
--- a/webapp/mixins/persistentLinks.js
+++ b/webapp/mixins/persistentLinks.js
@@ -26,7 +26,7 @@ export default function(options = {}) {
resource = response.data[Object.keys(response.data)[0]][0]
if (resource) return redirect(`/${path}/${resource.id}/${resource.slug}`)
- return error({ statusCode: 404, message })
+ return error({ statusCode: 404, key: message })
},
}
}
diff --git a/webapp/pages/post/_id.vue b/webapp/pages/post/_id.vue
index a02afd3b9..92795606f 100644
--- a/webapp/pages/post/_id.vue
+++ b/webapp/pages/post/_id.vue
@@ -35,7 +35,7 @@ const options = {
}
`,
path: 'post',
- message: 'This post could not be found',
+ message: 'error-pages.post-not-found',
}
const persistentLinks = PersistentLinks(options)
@@ -55,14 +55,14 @@ export default {
},
// TODO implement
/* {
- name: this.$t('common.letsTalk'),
- path: `/post/${id}/${slug}#lets-talk`
- }, */
+ name: this.$t('common.letsTalk'),
+ path: `/post/${id}/${slug}#lets-talk`
+ }, */
// TODO implement
/* {
- name: this.$t('common.versus'),
- path: `/post/${id}/${slug}#versus`
- } */
+ name: this.$t('common.versus'),
+ path: `/post/${id}/${slug}#versus`
+ } */
],
},
{
@@ -71,9 +71,9 @@ export default {
},
// TODO implement
/* {
- name: this.$t('common.takeAction'),
- path: `/post/${id}/${slug}/take-action`
- } */
+ name: this.$t('common.takeAction'),
+ path: `/post/${id}/${slug}/take-action`
+ } */
]
},
},
diff --git a/webapp/pages/post/edit/_id.vue b/webapp/pages/post/edit/_id.vue
index c79d2b70e..d269a04d8 100644
--- a/webapp/pages/post/edit/_id.vue
+++ b/webapp/pages/post/edit/_id.vue
@@ -38,7 +38,7 @@ export default {
variables: { id },
})
if (contribution.author.id !== store.getters['auth/user'].id) {
- error({ statusCode: 403, message: "You can't edit that!" })
+ error({ statusCode: 403, message: 'error-pages.cannot-edit-post' })
}
return { contribution }
},
diff --git a/webapp/pages/profile/_id.vue b/webapp/pages/profile/_id.vue
index 992e5efce..b9bbef83e 100644
--- a/webapp/pages/profile/_id.vue
+++ b/webapp/pages/profile/_id.vue
@@ -23,7 +23,7 @@ const options = {
}
}
`,
- message: 'This user could not be found',
+ message: 'error-pages.profile-not-found',
path: 'profile',
}
const persistentLinks = PersistentLinks(options)
diff --git a/webapp/static/img/svg/errors/error403.svg b/webapp/static/img/svg/errors/error403.svg
new file mode 100644
index 000000000..e52d00a7a
--- /dev/null
+++ b/webapp/static/img/svg/errors/error403.svg
@@ -0,0 +1 @@
+
diff --git a/webapp/static/img/svg/errors/error404.svg b/webapp/static/img/svg/errors/error404.svg
new file mode 100644
index 000000000..78f019410
--- /dev/null
+++ b/webapp/static/img/svg/errors/error404.svg
@@ -0,0 +1 @@
+
diff --git a/webapp/static/img/svg/errors/error500.svg b/webapp/static/img/svg/errors/error500.svg
new file mode 100644
index 000000000..5a95d7bf6
--- /dev/null
+++ b/webapp/static/img/svg/errors/error500.svg
@@ -0,0 +1 @@
+
diff --git a/webapp/static/img/svg/errors/error503.svg b/webapp/static/img/svg/errors/error503.svg
new file mode 100644
index 000000000..45b7b0cf7
--- /dev/null
+++ b/webapp/static/img/svg/errors/error503.svg
@@ -0,0 +1,15 @@
+
+