2021-02-03 18:00:49 +01:00
< template >
< div class = "container section" id = "error-wrapper" >
< div class = "column" >
< section >
< div class = "picture-wrapper" >
< picture >
< source
srcset = "
/ i m g / p i c s / e r r o r - 4 8 0 w . w e b p 1 x ,
/ i m g / p i c s / e r r o r - 1 0 2 4 w . w e b p 2 x
"
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 >
< b -message type = "is-danger" class = "is-size-5" >
< h1 >
{ {
$t (
"An error has occured. Sorry about that. You may try to reload the page."
)
} }
< / h1 >
< / b - m e s s a g e >
< / section >
< b -loading v -if = " $ apollo.loading " :active.sync ="$apollo.loading" / >
< section v-else >
< h2 class = "is-size-5" > { { $t ( "What can I do to help?" ) } } < / h2 >
< p class = "content" >
< i18n
tag = "span"
path = "{instanceName} is an instance of {mobilizon_link}, a free software built with the community."
>
< b slot = "instanceName" > { { config . name } } < / b >
< a slot = "mobilizon_link" href = "https://joinmobilizon.org" > { {
$t ( "Mobilizon" )
} } < / a >
< / i18n >
2021-12-16 16:48:50 +01:00
< span v-if ="sentryEnabled && sentryReady" >
{ {
$t (
"We collect your feedback and the error information in order to improve this service."
)
} } < / s p a n
>
< 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 >
2021-02-03 18:00:49 +01:00
< / p >
2021-12-16 16:48:50 +01:00
< form
v - if = "sentryEnabled && sentryReady && !submittedFeedback"
@ submit . prevent = "sendErrorToSentry"
>
< b -field : label = "$t('What happened?')" label -for = " what -happened " >
< b -input
v - model = "feedback"
type = "textarea"
id = "what-happened"
: placeholder = "$t(`I've clicked on X, then on Y`)"
/ >
< / b - f i e l d >
< b -button icon -left = " send " native -type = " submit " type = "is-primary" > { {
$t ( "Send feedback" )
} } < / b - b u t t o n >
< p class = "content" >
{ {
$t (
"Please add as many details as possible to help identify the problem."
)
} }
< / p >
< / form >
< b -message type = "is-danger" v -else -if = " feedbackError " >
< p >
{ {
$t (
"Sorry, we wen't able to save your feedback. Don't worry, we'll try to fix this issue anyway."
)
} }
< / p >
< i18n path = "You may now close this page or {return_to_the_homepage}." >
< template # return_to_the_homepage >
< router -link : to = "{ name: RouteName.HOME }" > { {
$t ( "return to the homepage" )
} } < / r o u t e r - l i n k >
< / template >
< / i18n >
< / b - m e s s a g e >
< b -message type = "is-success" v -else -if = " submittedFeedback " >
< p > { { $t ( "Thanks a lot, your feedback was submitted!" ) } } < / p >
< i18n path = "You may now close this page or {return_to_the_homepage}." >
< template # return_to_the_homepage >
< router -link : to = "{ name: RouteName.HOME }" > { {
$t ( "return to the homepage" )
} } < / r o u t e r - l i n k >
< / template >
< / i18n >
< / b - m e s s a g e >
< div
class = "content"
v - if = "!(sentryEnabled && sentryReady) || submittedFeedback"
>
< p v-if ="submittedFeedback" > {{ $ t ( " You may also : " ) }} < / p >
2021-02-03 18:00:49 +01:00
< ul >
< li >
< a
href = "https://framacolibri.org/c/mobilizon/39"
target = "_blank"
> { { $t ( "Open a topic on our forum" ) } } < / a
>
< / li >
< li >
< a
2021-12-16 16:48:50 +01:00
href = "https://framagit.org/framasoft/mobilizon/-/issues/"
2021-02-03 18:00:49 +01:00
target = "_blank"
> { {
$t ( "Open an issue on our bug tracker (advanced users)" )
} } < / a
>
< / li >
< / ul >
< / div >
2021-12-16 16:48:50 +01:00
< p class = "content" v-if ="!sentryEnabled" >
2021-02-03 18:00:49 +01:00
{ {
$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 >
2021-12-16 16:48:50 +01:00
< p v-if ="!sentryEnabled" >
2021-02-03 18:00:49 +01:00
{ {
$t (
"The technical details of the error can help developers solve the problem more easily. Please add them to your feedback."
)
} }
< / p >
2021-12-16 16:48:50 +01:00
< div class = "buttons" v-if ="!sentryEnabled" >
2021-02-03 18:00:49 +01:00
< b -tooltip
: label = "tooltipConfig.label"
: type = "tooltipConfig.type"
: active = "copied !== false"
always
>
2021-10-10 16:24:12 +02:00
< b -button
@ click = "copyErrorToClipboard"
@ keyup . enter = "copyErrorToClipboard"
> { { $t ( "Copy details to clipboard" ) } } < / b - b u t t o n
>
2021-02-03 18:00:49 +01:00
< / b - t o o l t i p >
< / div >
< / section >
< / div >
< / div >
< / template >
< script lang = "ts" >
2021-12-16 16:48:50 +01:00
import { CONFIG } from "@/graphql/config" ;
import { checkProviderConfig , convertConfig } from "@/services/statistics" ;
import { IAnalyticsConfig , IConfig } from "@/types/config.model" ;
2021-02-03 18:00:49 +01:00
import { Component , Prop , Vue } from "vue-property-decorator" ;
2021-12-16 16:48:50 +01:00
import { LOGGED _USER } from "@/graphql/user" ;
import { IUser } from "@/types/current-user.model" ;
import { ISentryConfiguration } from "@/types/analytics/sentry.model" ;
import { submitFeedback } from "@/services/statistics/sentry" ;
import RouteName from "@/router/name" ;
2021-02-03 18:00:49 +01:00
@ Component ( {
apollo : {
2021-12-16 16:48:50 +01:00
config : CONFIG ,
loggedUser : LOGGED _USER ,
2021-02-03 18:00:49 +01:00
} ,
metaInfo ( ) {
return {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
title : this . $t ( "Error" ) as string ,
titleTemplate : "%s | Mobilizon" ,
} ;
} ,
} )
export default class ErrorComponent extends Vue {
@ Prop ( { required : true , type : Error } ) error ! : Error ;
copied : "success" | "error" | false = false ;
2021-12-16 16:48:50 +01:00
config ! : IConfig ;
feedback = "" ;
submittedFeedback = false ;
feedbackError = false ;
loggedUser ! : IUser ;
RouteName = RouteName ;
2021-02-03 18:00:49 +01:00
async copyErrorToClipboard ( ) : Promise < void > {
try {
if ( window . isSecureContext && navigator . clipboard ) {
await navigator . clipboard . writeText ( this . fullErrorString ) ;
} else {
this . fallbackCopyTextToClipboard ( this . fullErrorString ) ;
}
this . copied = "success" ;
setTimeout ( ( ) => {
this . copied = false ;
} , 2000 ) ;
} catch ( e ) {
this . copied = "error" ;
console . error ( "Unable to copy to clipboard" ) ;
console . error ( e ) ;
}
}
get fullErrorString ( ) : string {
return ` ${ this . error . name } : ${ this . error . message } \ n \ n ${ this . error . stack } ` ;
}
get tooltipConfig ( ) : { label : string | null ; type : string | null } {
if ( this . copied === "success" )
return {
label : this . $t ( "Error details copied!" ) as string ,
type : "is-success" ,
} ;
if ( this . copied === "error" )
return {
label : this . $t ( "Unable to copy to clipboard" ) as string ,
type : "is-danger" ,
} ;
return { label : null , type : "is-primary" } ;
}
private 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 ) ;
}
2021-12-16 16:48:50 +01:00
get sentryEnabled ( ) : boolean {
return this . sentryProvider ? . enabled === true ;
}
get sentryProvider ( ) : IAnalyticsConfig | undefined {
return this . config && checkProviderConfig ( this . config , "sentry" ) ;
}
get sentryConfig ( ) : ISentryConfiguration | undefined {
if ( this . sentryProvider ? . configuration ) {
return convertConfig (
this . sentryProvider ? . configuration
) as ISentryConfiguration ;
}
return undefined ;
}
get sentryReady ( ) {
const eventId = window . sessionStorage . getItem ( "lastEventId" ) ;
const dsn = this . sentryConfig ? . dsn ;
const organization = this . sentryConfig ? . organization ;
const project = this . sentryConfig ? . project ;
const host = this . sentryConfig ? . host ;
return eventId && dsn && organization && project && host ;
}
async sendErrorToSentry ( ) {
try {
const eventId = window . sessionStorage . getItem ( "lastEventId" ) ;
const dsn = this . sentryConfig ? . dsn ;
const organization = this . sentryConfig ? . organization ;
const project = this . sentryConfig ? . project ;
const host = this . sentryConfig ? . host ;
const endpoint = ` https:// ${ host } /api/0/projects/ ${ organization } / ${ project } /user-feedback/ ` ;
if ( eventId && dsn && this . sentryReady ) {
await submitFeedback ( endpoint , dsn , {
event _id : eventId ,
name :
this . loggedUser ? . defaultActor ? . preferredUsername || "Unknown user" ,
email : this . loggedUser ? . email || "unknown@email.org" ,
comments : this . feedback ,
} ) ;
this . submittedFeedback = true ;
}
} catch ( error ) {
console . error ( error ) ;
this . feedbackError = true ;
}
}
2021-02-03 18:00:49 +01:00
}
< / script >
< style lang = "scss" scoped >
# error - wrapper {
width : 100 % ;
background : $white ;
section {
margin - bottom : 2 rem ;
}
. picture - wrapper {
text - align : center ;
}
details {
summary : hover {
cursor : pointer ;
}
}
}
< / style >