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 @@ + + + + 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 @@ + + + + error503 + Created with Sketch. + + + + + + + + + +