diff --git a/package-lock.json b/package-lock.json index 37d98b6c8..3dc2d1c32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8705,6 +8705,11 @@ "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=" }, + "detect-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.2.0.tgz", + "integrity": "sha512-tr7XntDAu50BVENgQfajMLzacmSe34D+qZc4zjnniz0ZVuw/TZcLcyxHQjYpJTM36sGEkZZlYLnIM1hH7alTMA==" + }, "dns-packet": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz", @@ -19359,6 +19364,21 @@ } } }, + "serialize-error": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", + "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", + "requires": { + "type-fest": "^0.20.2" + }, + "dependencies": { + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" + } + } + }, "serialize-javascript": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", diff --git a/package.json b/package.json index fcd92f8b5..c21f26839 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "connect": "^3.7.0", "core-js": "^3.15.1", "date-fns": "^2.22.1", + "detect-browser": "^5.2.0", "fontsource-ibm-plex-sans": "^3.1.5", "i18n": "^0.13.3", "is-fqdn": "^2.0.1", @@ -48,6 +49,7 @@ "lru-cache": "^6.0.0", "node-fetch": "^2.6.1", "normalize.css": "^8.0.1", + "serialize-error": "^8.1.0", "serve-static": "^1.14.1", "sophia-bonding-curve": "github:aeternity/BondingCurve#1.0.0-alpha.2", "soundcloud-widget": "^0.2.1", diff --git a/src/App.vue b/src/App.vue index 52606a6a2..0790936a3 100644 --- a/src/App.vue +++ b/src/App.vue @@ -101,7 +101,11 @@ export default { console.log('found wallet'); this.useSdkWallet(); this.setAddress(address); - this.$store.dispatch('updateCookiesConsent'); + try { + await this.$store.dispatch('updateCookiesConsent'); + } catch (error) { + if (error.message !== 'Operation rejected by user') throw error; + } } else { this.setAddress(address); } diff --git a/src/assets/iconCodeError.svg b/src/assets/iconCodeError.svg new file mode 100644 index 000000000..45dc8f973 --- /dev/null +++ b/src/assets/iconCodeError.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + diff --git a/src/assets/iconThumbUp.svg b/src/assets/iconThumbUp.svg new file mode 100644 index 000000000..d636e2a6f --- /dev/null +++ b/src/assets/iconThumbUp.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/components/AeButton.vue b/src/components/AeButton.vue index c77b21665..fdde5dd14 100644 --- a/src/components/AeButton.vue +++ b/src/components/AeButton.vue @@ -49,6 +49,7 @@ export default { transition: background-color 0.3s; text-align: center; font-size: 16px; + cursor: pointer; &[disabled] { opacity: 0.4; diff --git a/src/components/AlertModal.vue b/src/components/AlertModal.vue index 8dc284422..7538ee7f4 100644 --- a/src/components/AlertModal.vue +++ b/src/components/AlertModal.vue @@ -1,108 +1,54 @@ - - - - - {{ title }} - - {{ row }} - - - {{ primaryButtonText }} - - - - + + + {{ row }} + + + {{ primaryButtonText }} + + diff --git a/src/components/CookiesDialog.vue b/src/components/CookiesDialog.vue index 46816fd4b..7fef77ae6 100644 --- a/src/components/CookiesDialog.vue +++ b/src/components/CookiesDialog.vue @@ -71,8 +71,12 @@ export default { }, methods: { async allowHandler() { - await this.$store.dispatch('backend/setCookies', { scope: this.scope, status: true }); - this.resolve(); + try { + await this.$store.dispatch('backend/setCookies', { scope: this.scope, status: true }); + this.resolve(); + } catch (error) { + if (error.message !== 'Operation rejected by user') throw error; + } }, }, }; diff --git a/src/components/Dialog.vue b/src/components/Dialog.vue new file mode 100644 index 000000000..266262b0b --- /dev/null +++ b/src/components/Dialog.vue @@ -0,0 +1,117 @@ + + + + + + + + + + + + {{ title }} + + {{ subtitle }} + + + + + + + + + diff --git a/src/components/ErrorReportModal.vue b/src/components/ErrorReportModal.vue new file mode 100644 index 000000000..08248b4b4 --- /dev/null +++ b/src/components/ErrorReportModal.vue @@ -0,0 +1,139 @@ + + + + + {{ $t('reportBug.Description') }} + + + + {{ $t('reportBug.TechnicalDetails') }} + + {{ JSON.stringify(report, null, 2) }} + + {{ $t('reportBug.DescriptionTitle') }} + + + + {{ $t('reportBug.Cancel') }} + + + {{ $t('reportBug.Send') }} + + + + + + + diff --git a/src/components/ErrorReportSubmittedModal.vue b/src/components/ErrorReportSubmittedModal.vue new file mode 100644 index 000000000..9ba09ebad --- /dev/null +++ b/src/components/ErrorReportSubmittedModal.vue @@ -0,0 +1,35 @@ + + + + {{ $t('reportBug.Success.Description') }} + + {{ $t('reportBug.Success.Return') }} + + + + + + + diff --git a/src/components/UserInfo.vue b/src/components/UserInfo.vue index ed9bab1e6..b9088ca92 100644 --- a/src/components/UserInfo.vue +++ b/src/components/UserInfo.vue @@ -175,7 +175,7 @@ :key="scope" class="cookies-button" :class="{ active: cookiesConsent[scope] }" - @click="setCookies({ scope, status: !cookiesConsent[scope] })" + @click="toggleCookiesConsent(scope)" > {{ scope }} @@ -382,6 +382,16 @@ export default { this.profile.biography = this.profile.biography || ''; if (this.isOwn) this.$store.commit('setUserProfile', this.profile); }, + async toggleCookiesConsent(scope) { + try { + await this.$store.dispatch( + 'backend/setCookies', + { scope, status: !this.cookiesConsent[scope] }, + ); + } catch (error) { + if (error.message !== 'Operation rejected by user') throw error; + } + }, }, }; diff --git a/src/locales/en.json b/src/locales/en.json index 63f47231b..70620f2f8 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -651,5 +651,21 @@ "MenuLink": "Meet", "Placeholder": "Room name", "Start": "Start" + }, + "reportBug": { + "Title": "Unhandled error", + "SubTitle": "Would you like to submit a bug report?", + "TechnicalDetails": "Technical details", + "Description": "We have encountered an error while loading this page. Be a fellow superhero and help us improve our service by sending us a bug report. Only technical details and no personal data will be submitted.", + "DescriptionTitle": "Bug description", + "Send": "Submit", + "Cancel": "Cancel", + "Placeholder": "Describe the bug you encountered (optional)", + "Success": { + "Title": "Thank you!", + "SubTitle": "Your bug report has been submitted.", + "Description": "Thank you for being such a wonderful fellow superhero. The information provided will help us to improve our product and services for the benefit of the whole Superhero community.", + "Return": "Return to website" + } } } diff --git a/src/locales/es.json b/src/locales/es.json index 30c2b914c..d5650f723 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -647,5 +647,21 @@ "MenuLink": "Encontrar", "Placeholder": "Nombre de la habitación", "Start": "Empieza" + }, + "reportBug": { + "Title": "Unhandled error", + "SubTitle": "Would you like to submit a bug report?", + "TechnicalDetails": "Technical details", + "Description": "We have encountered an error while loading this page. Be a fellow superhero and help us improve our service by sending us a bug report. Only technical details and no personal data will be submitted.", + "DescriptionTitle": "Bug description", + "Send": "Submit", + "Cancel": "Cancel", + "Placeholder": "Describe the bug you encountered (optional)", + "Success": { + "Title": "Thank you!", + "SubTitle": "Your bug report has been submitted.", + "Description": "Thank you for being such a wonderful fellow superhero. The information provided will help us to improve our product and services for the benefit of the whole Superhero community.", + "Return": "Return to website" + } } } diff --git a/src/locales/fr.json b/src/locales/fr.json index 612b62518..c7426761d 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -637,5 +637,21 @@ "MenuLink": "Rencontrer", "Placeholder": "Nom de la pièce", "Start": "Commencer" + }, + "reportBug": { + "Title": "Unhandled error", + "SubTitle": "Would you like to submit a bug report?", + "TechnicalDetails": "Technical details", + "Description": "We have encountered an error while loading this page. Be a fellow superhero and help us improve our service by sending us a bug report. Only technical details and no personal data will be submitted.", + "DescriptionTitle": "Bug description", + "Send": "Submit", + "Cancel": "Cancel", + "Placeholder": "Describe the bug you encountered (optional)", + "Success": { + "Title": "Thank you!", + "SubTitle": "Your bug report has been submitted.", + "Description": "Thank you for being such a wonderful fellow superhero. The information provided will help us to improve our product and services for the benefit of the whole Superhero community.", + "Return": "Return to website" + } } } diff --git a/src/locales/zh.json b/src/locales/zh.json index 35ac40ed5..7e1429b9c 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -577,5 +577,21 @@ "MenuLink": "菜单", "Placeholder": "名称", "Start": "开始" + }, + "reportBug": { + "Title": "Unhandled error", + "SubTitle": "Would you like to submit a bug report?", + "TechnicalDetails": "Technical details", + "Description": "We have encountered an error while loading this page. Be a fellow superhero and help us improve our service by sending us a bug report. Only technical details and no personal data will be submitted.", + "DescriptionTitle": "Bug description", + "Send": "Submit", + "Cancel": "Cancel", + "Placeholder": "Describe the bug you encountered (optional)", + "Success": { + "Title": "Thank you!", + "SubTitle": "Your bug report has been submitted.", + "Description": "Thank you for being such a wonderful fellow superhero. The information provided will help us to improve our product and services for the benefit of the whole Superhero community.", + "Return": "Return to website" + } } } diff --git a/src/store/index.js b/src/store/index.js index 9103b84ee..d95d1efce 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -7,6 +7,7 @@ import { camelCase } from 'lodash-es'; import mutations from './mutations'; import getters from './getters'; import modals from './plugins/modals'; +import errorHandler from './plugins/errorHandler'; import backend from './modules/backend'; import aeternity from './modules/aeternity'; import Backend from '../utils/backend'; @@ -42,8 +43,11 @@ export default () => new Vuex.Store({ commit('setPinnedItems', await Backend.getPinnedItems(address)); }, async updateCookiesConsent({ commit, dispatch }) { - dispatch('backend/callWithAuth', { method: 'getCookiesConsent' }) - .then((list) => list.forEach(({ scope, status }) => commit('setCookiesConsent', { scope, status: status === 'ALLOWED' }))); + const list = await dispatch('backend/callWithAuth', { method: 'getCookiesConsent' }); + list.forEach(({ scope, status }) => commit('setCookiesConsent', { + scope, + status: status === 'ALLOWED', + })); }, async getTokenBalance({ state: { address, middleware } }, contractAddress) { const result = await middleware.getAex9Balance(contractAddress, address); @@ -124,5 +128,6 @@ export default () => new Vuex.Store({ }, plugins: [ modals, + errorHandler, ], }); diff --git a/src/store/modules/backend.js b/src/store/modules/backend.js index 54b3062fb..846dcc53a 100644 --- a/src/store/modules/backend.js +++ b/src/store/modules/backend.js @@ -130,8 +130,7 @@ export default { const { challenge } = await Backend[method](address, arg); if (useSdkWallet) { const signature = await sdk.signMessage(challenge); - const response = await Backend[method](address, { challenge, signature }); - return response; + return Backend[method](address, { challenge, signature }); } const url = new URL(to || window.location, window.location); diff --git a/src/store/plugins/errorHandler.js b/src/store/plugins/errorHandler.js new file mode 100644 index 000000000..ea3712603 --- /dev/null +++ b/src/store/plugins/errorHandler.js @@ -0,0 +1,57 @@ +import { serializeError } from 'serialize-error'; +import Vue from 'vue'; +import { detect } from 'detect-browser'; + +export default ({ dispatch }) => { + const reportsToSend = []; + let showingErrorReportModal = false; + + const handleError = async (error) => { + reportsToSend.push({ + appVersion: process.env.npm_package_version, + browser: detect(), + platform: 'superhero-ui', + time: Date.now(), + error: { + ...error, + appRevision: process.env.COMMIT_HASH, + location: window.location.href, + }, + }); + if (showingErrorReportModal) return; + showingErrorReportModal = true; + let isReportSubmitted = false; + while (reportsToSend.length) { + // eslint-disable-next-line no-await-in-loop + isReportSubmitted = await dispatch('modals/open', { + name: 'error-report-modal', + report: reportsToSend.shift(), + }) || isReportSubmitted; + } + if (isReportSubmitted) { + await dispatch('modals/open', { name: 'error-report-submitted-modal' }); + } + showingErrorReportModal = false; + }; + + window.addEventListener('unhandledrejection', (event) => handleError({ + exceptionType: 'unhandled-rejection', + ...serializeError(event.reason), + })); + window.onerror = (message, source, line, col, error) => handleError({ + exceptionType: 'unhandled-error', + message, + source, + line, + col, + ...serializeError(error), + }); + Vue.config.errorHandler = async (error, vm, info) => { + console.error(error); + await handleError({ + exceptionType: 'unhandled-vue-error', + info, + ...serializeError(error), + }); + }; +}; diff --git a/src/utils/backend.js b/src/utils/backend.js index 192ef55f5..a57087b97 100644 --- a/src/utils/backend.js +++ b/src/utils/backend.js @@ -160,4 +160,12 @@ export default class Backend { body: JSON.stringify(postParam), headers: { 'Content-Type': 'application/json' }, }); + + static errorReport = (report) => { + backendFetch('errorreport', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(report), + }); + }; } diff --git a/src/views/modals.js b/src/views/modals.js index 384635c0c..ebd531533 100644 --- a/src/views/modals.js +++ b/src/views/modals.js @@ -7,6 +7,8 @@ import FeedItemMenu from '../components/FeedItemMenu.vue'; import TokenSelect from '../components/TokenSelect.vue'; import TipInputPopup from '../components/TipInputPopup.vue'; import CookiesDialog from '../components/CookiesDialog.vue'; +import ErrorReportModal from '../components/ErrorReportModal.vue'; +import ErrorReportSubmittedModal from '../components/ErrorReportSubmittedModal.vue'; export default () => { registerModal({ name: 'success', component: AlertModal, allowRedirect: true }); @@ -48,4 +50,14 @@ export default () => { component: CookiesDialog, resolveNullOnRedirect: true, }); + registerModal({ + name: 'error-report-modal', + component: ErrorReportModal, + allowRedirect: true, + }); + registerModal({ + name: 'error-report-submitted-modal', + component: ErrorReportSubmittedModal, + allowRedirect: true, + }); };
- {{ row }} -
+ {{ row }} +
{{ JSON.stringify(report, null, 2) }}