From 676e664933ddf7d3df1c52c83c75ec29305aa3bf Mon Sep 17 00:00:00 2001 From: Clockwork Date: Thu, 2 May 2024 16:21:42 +0300 Subject: [PATCH] feat: Polish UX for unrecoverable exceptions (#87) * feat: Add error popup * chore: catch top-level exceptions --- src/App.vue | 2 + src/components/common/MarkdownParser.vue | 21 ++-- src/components/home/CommentCount.vue | 9 +- src/components/popups/ErrorBox.vue | 50 +++++++++ src/components/proposals/ProposalWrapper.vue | 39 +++---- src/localization/index.ts | 5 + src/views/HomeView.vue | 102 +++++++++++-------- src/views/ProposalView.vue | 16 +-- 8 files changed, 168 insertions(+), 76 deletions(-) create mode 100644 src/components/popups/ErrorBox.vue diff --git a/src/App.vue b/src/App.vue index b8cd6d1..3ef9e8f 100644 --- a/src/App.vue +++ b/src/App.vue @@ -2,6 +2,7 @@ import { RouterView } from "vue-router"; import HeaderSection from "@/components/layout/HeaderSection.vue"; +import ErrorBox from "@/components/popups/ErrorBox.vue"; import FooterSection from "@/components/layout/FooterSection.vue"; import { useGithubDiscussions } from "@/composables/useGithubDiscussions"; @@ -13,6 +14,7 @@ useGithubDiscussions().setup();
+ diff --git a/src/components/common/MarkdownParser.vue b/src/components/common/MarkdownParser.vue index 02bac8f..abecd26 100644 --- a/src/components/common/MarkdownParser.vue +++ b/src/components/common/MarkdownParser.vue @@ -5,6 +5,7 @@ import MarkdownItMermaid from "@agoose77/markdown-it-mermaid"; import { alertPlugin } from "markdown-it-github-alert"; import DOMPurify from "dompurify"; +import { bus } from "@/bus"; const md = markdownit({ html: true, @@ -19,15 +20,19 @@ const content = defineModel(); const trimmedContent = ref(""); async function parseData() { - if (!content.value) { - return ""; + try { + if (!content.value) { + return ""; + } + + const htmlContent = props.limit + ? await md.render(content.value.slice(0, props.limit)) + : await md.render(content.value); + + trimmedContent.value = DOMPurify.sanitize(htmlContent); + } catch (_e) { + bus.emit("error"); } - - const htmlContent = props.limit - ? await md.render(content.value.slice(0, props.limit)) - : await md.render(content.value); - - trimmedContent.value = DOMPurify.sanitize(htmlContent); } const getClasses = computed(() => { diff --git a/src/components/home/CommentCount.vue b/src/components/home/CommentCount.vue index 32e8b58..b41115f 100644 --- a/src/components/home/CommentCount.vue +++ b/src/components/home/CommentCount.vue @@ -1,4 +1,5 @@ diff --git a/src/components/popups/ErrorBox.vue b/src/components/popups/ErrorBox.vue new file mode 100644 index 0000000..8ca98e3 --- /dev/null +++ b/src/components/popups/ErrorBox.vue @@ -0,0 +1,50 @@ + + + diff --git a/src/components/proposals/ProposalWrapper.vue b/src/components/proposals/ProposalWrapper.vue index 14a2383..2c3c69b 100644 --- a/src/components/proposals/ProposalWrapper.vue +++ b/src/components/proposals/ProposalWrapper.vue @@ -61,25 +61,30 @@ const validatorsWithStakeAndVotes = ref< >([]); watch(validators, async (valSet, _old) => { - validatorsWithStakeAndVotes.value = await Promise.all( - valSet.map(async (val) => { - if (val.validator.validator_info && val.validator.validator_info.self_delegate_address) { - const vp = await getVotingPower(val.validator.validator_info.self_delegate_address); - const votes = await getVotesAsync(val.validator.validator_info.self_delegate_address, props.proposalId); - if (votes && votes.proposal_vote.length > 0) { - return { - ...val, - voting_power: vp, - votes: votes.proposal_vote.filter((x) => x.height == votes.proposal_vote[0].height), - }; + try { + validatorsWithStakeAndVotes.value = await Promise.all( + valSet.map(async (val) => { + if (val.validator.validator_info && val.validator.validator_info.self_delegate_address) { + const vp = await getVotingPower(val.validator.validator_info.self_delegate_address); + const votes = await getVotesAsync(val.validator.validator_info.self_delegate_address, props.proposalId); + if (votes && votes.proposal_vote.length > 0) { + return { + ...val, + voting_power: vp, + votes: votes.proposal_vote.filter((x) => x.height == votes.proposal_vote[0].height), + }; + } else { + return { ...val, voting_power: vp, votes: [] }; + } } else { - return { ...val, voting_power: vp, votes: [] }; + return { ...val, voting_power: 0, votes: [] }; } - } else { - return { ...val, voting_power: 0, votes: [] }; - } - }), - ); + }), + ); + } catch (_e) { + bus.emit("error"); + 1; + } }); const maxValidators = computed(() => { diff --git a/src/localization/index.ts b/src/localization/index.ts index c778382..569d996 100644 --- a/src/localization/index.ts +++ b/src/localization/index.ts @@ -70,6 +70,11 @@ export const messages = { }, }, components: { + ErrorBox: { + title: "Error", + message: "Something went wrong...", + cta: "Please refresh", + }, WalletConnect: { button: "Connect Wallet", cta: "Connect your wallet", diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index a047060..d66db19 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -12,6 +12,7 @@ import apolloClient from "@/apolloClient"; import { useChainData } from "@/composables/useChainData"; import { useTelemetry } from "@/composables/useTelemetry"; +import { bus } from "@/bus"; const typeFilterIndex = ref(0); const activityFilterIndex = ref(0); @@ -63,46 +64,57 @@ const filterToStatus = computed(() => { watch(filterToStatus, async (newType, oldType) => { if (newType !== oldType) { provideApolloClient(apolloClient); - - const res = await getProposalsAsync( - sortToOrder.value, - limit.value, - offset.value, - newType ?? undefined, - searchString.value, - ); - if (res) { - proposals.value = res; + try { + const res = await getProposalsAsync( + sortToOrder.value, + limit.value, + offset.value, + newType ?? undefined, + searchString.value, + ); + if (res) { + proposals.value = res; + } + } catch (_e) { + bus.emit("error"); } } }); watch(searchString, async (newSearch, oldSearch) => { if (newSearch !== oldSearch) { provideApolloClient(apolloClient); - const res = await getProposalsAsync( - sortToOrder.value, - limit.value, - offset.value, - filterToStatus.value ?? undefined, - searchString.value, - ); - if (res) { - proposals.value = res; + try { + const res = await getProposalsAsync( + sortToOrder.value, + limit.value, + offset.value, + filterToStatus.value ?? undefined, + searchString.value, + ); + if (res) { + proposals.value = res; + } + } catch (_e) { + bus.emit("error"); } } }); watch(sortToOrder, async (newOrder, oldOrder) => { if (newOrder !== oldOrder) { provideApolloClient(apolloClient); - const res = await getProposalsAsync( - sortToOrder.value, - limit.value, - offset.value, - filterToStatus.value ?? undefined, - searchString.value, - ); - if (res) { - proposals.value = res; + try { + const res = await getProposalsAsync( + sortToOrder.value, + limit.value, + offset.value, + filterToStatus.value ?? undefined, + searchString.value, + ); + if (res) { + proposals.value = res; + } + } catch (_e) { + bus.emit("error"); } } }); @@ -128,22 +140,26 @@ function prev() { watch(offset, async (newOffset, oldOffset) => { if (newOffset != oldOffset) { provideApolloClient(apolloClient); - if (filterToStatus.value != null) { - const res = await getProposalsAsync( - sortToOrder.value, - limit.value, - newOffset, - filterToStatus.value, - searchString.value, - ); - if (res) { - proposals.value = res; - } - } else { - const res = await getProposalsAsync(sortToOrder.value, limit.value, newOffset, undefined, searchString.value); - if (res) { - proposals.value = res; + try { + if (filterToStatus.value != null) { + const res = await getProposalsAsync( + sortToOrder.value, + limit.value, + newOffset, + filterToStatus.value, + searchString.value, + ); + if (res) { + proposals.value = res; + } + } else { + const res = await getProposalsAsync(sortToOrder.value, limit.value, newOffset, undefined, searchString.value); + if (res) { + proposals.value = res; + } } + } catch (_e) { + bus.emit("error"); } } }); diff --git a/src/views/ProposalView.vue b/src/views/ProposalView.vue index 1ef20b3..2bb1c55 100644 --- a/src/views/ProposalView.vue +++ b/src/views/ProposalView.vue @@ -1,5 +1,6 @@