Migrate to Vue 3 and Vite
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
217
js/src/components/ErrorComponent.vue
Normal file
217
js/src/components/ErrorComponent.vue
Normal file
@@ -0,0 +1,217 @@
|
||||
<template>
|
||||
<div class="container mx-auto" id="error-wrapper">
|
||||
<div class="">
|
||||
<section>
|
||||
<div class="text-center">
|
||||
<picture>
|
||||
<source
|
||||
:srcset="`/img/pics/error-480w.webp 1x, /img/pics/error-1024w.webp 2x`"
|
||||
type="image/webp"
|
||||
/>
|
||||
<source
|
||||
:srcset="`/img/pics/error-480w.jpg 1x, /img/pics/error-1024w.jpg 2x`"
|
||||
type="image/jpeg"
|
||||
/>
|
||||
|
||||
<img
|
||||
:src="`/img/pics/error-480w.jpg`"
|
||||
alt=""
|
||||
width="480"
|
||||
height="312"
|
||||
loading="lazy"
|
||||
/>
|
||||
</picture>
|
||||
</div>
|
||||
<o-notification variant="danger" class="">
|
||||
<h1>
|
||||
{{
|
||||
t(
|
||||
"An error has occured. Sorry about that. You may try to reload the page."
|
||||
)
|
||||
}}
|
||||
</h1>
|
||||
</o-notification>
|
||||
</section>
|
||||
<o-loading v-if="loading" v-model:active="loading" />
|
||||
<section v-else>
|
||||
<h2 class="">{{ t("What can I do to help?") }}</h2>
|
||||
<p class="prose dark:prose-invert">
|
||||
<i18n-t
|
||||
tag="span"
|
||||
keypath="{instanceName} is an instance of {mobilizon_link}, a free software built with the community."
|
||||
>
|
||||
<template v-slot:instanceName>
|
||||
<b>{{ config?.name }}</b>
|
||||
</template>
|
||||
<template v-slot:mobilizon_link>
|
||||
<a href="https://joinmobilizon.org">{{ t("Mobilizon") }}</a>
|
||||
</template>
|
||||
</i18n-t>
|
||||
<span v-if="sentryEnabled">
|
||||
{{
|
||||
t(
|
||||
"We collect your feedback and the error information in order to improve this service."
|
||||
)
|
||||
}}</span
|
||||
>
|
||||
<span v-else>
|
||||
{{
|
||||
t(
|
||||
"We improve this software thanks to your feedback. To let us know about this issue, two possibilities (both unfortunately require user account creation):"
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</p>
|
||||
<SentryFeedback />
|
||||
|
||||
<p class="prose dark:prose-invert" v-if="!sentryEnabled">
|
||||
{{
|
||||
t(
|
||||
"Please add as many details as possible to help identify the problem."
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
|
||||
<details>
|
||||
<summary class="is-size-5">{{ t("Technical details") }}</summary>
|
||||
<p>{{ t("Error message") }}</p>
|
||||
<pre>{{ error }}</pre>
|
||||
<p>{{ t("Error stacktrace") }}</p>
|
||||
<pre>{{ error.stack }}</pre>
|
||||
</details>
|
||||
<p v-if="!sentryEnabled">
|
||||
{{
|
||||
t(
|
||||
"The technical details of the error can help developers solve the problem more easily. Please add them to your feedback."
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<div class="buttons" v-if="!sentryEnabled">
|
||||
<o-tooltip
|
||||
:label="tooltipConfig.label"
|
||||
:type="tooltipConfig.type"
|
||||
:active="copied !== false"
|
||||
always
|
||||
>
|
||||
<o-button
|
||||
@click="copyErrorToClipboard"
|
||||
@keyup.enter="copyErrorToClipboard"
|
||||
>{{ t("Copy details to clipboard") }}</o-button
|
||||
>
|
||||
</o-tooltip>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { checkProviderConfig } from "@/services/statistics";
|
||||
import { IAnalyticsConfig } from "@/types/config.model";
|
||||
import { computed, defineAsyncComponent, ref } from "vue";
|
||||
import { useQueryLoading } from "@vue/apollo-composable";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useAnalytics } from "@/composition/apollo/config";
|
||||
const SentryFeedback = defineAsyncComponent(
|
||||
() => import("./Feedback/SentryFeedback.vue")
|
||||
);
|
||||
|
||||
const { analytics } = useAnalytics();
|
||||
|
||||
const loading = useQueryLoading();
|
||||
|
||||
const props = defineProps<{
|
||||
error: Error;
|
||||
}>();
|
||||
|
||||
const copied = ref<"success" | "error" | false>(false);
|
||||
|
||||
const { t } = useI18n({ useScope: "global" });
|
||||
useHead({
|
||||
title: computed(() => t("Error")),
|
||||
});
|
||||
|
||||
const copyErrorToClipboard = async (): Promise<void> => {
|
||||
try {
|
||||
if (window.isSecureContext && navigator.clipboard) {
|
||||
await navigator.clipboard.writeText(fullErrorString.value);
|
||||
} else {
|
||||
fallbackCopyTextToClipboard(fullErrorString.value);
|
||||
}
|
||||
copied.value = "success";
|
||||
setTimeout(() => {
|
||||
copied.value = false;
|
||||
}, 2000);
|
||||
} catch (e) {
|
||||
copied.value = "error";
|
||||
console.error("Unable to copy to clipboard");
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
const fullErrorString = computed((): string => {
|
||||
return `${props.error.name}: ${props.error.message}\n\n${props.error.stack}`;
|
||||
});
|
||||
|
||||
const tooltipConfig = computed(
|
||||
(): { label: string | null; variant: string | null } => {
|
||||
if (copied.value === "success")
|
||||
return {
|
||||
label: t("Error details copied!") as string,
|
||||
variant: "success",
|
||||
};
|
||||
if (copied.value === "error")
|
||||
return {
|
||||
label: t("Unable to copy to clipboard") as string,
|
||||
variant: "danger",
|
||||
};
|
||||
return { label: null, variant: "primary" };
|
||||
}
|
||||
);
|
||||
|
||||
const fallbackCopyTextToClipboard = (text: string): void => {
|
||||
const textArea = document.createElement("textarea");
|
||||
textArea.value = text;
|
||||
|
||||
// Avoid scrolling to bottom
|
||||
textArea.style.top = "0";
|
||||
textArea.style.left = "0";
|
||||
textArea.style.position = "fixed";
|
||||
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
|
||||
document.execCommand("copy");
|
||||
|
||||
document.body.removeChild(textArea);
|
||||
};
|
||||
|
||||
const sentryEnabled = computed((): boolean => {
|
||||
return sentryProvider.value?.enabled === true;
|
||||
});
|
||||
|
||||
const sentryProvider = computed((): IAnalyticsConfig | undefined => {
|
||||
return checkProviderConfig(analytics.value ?? [], "sentry");
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
#error-wrapper {
|
||||
width: 100%;
|
||||
background: $white;
|
||||
|
||||
section {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.picture-wrapper {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
details {
|
||||
summary:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user