2022-01-10 14:17:52 -08:00
import React from 'react' ;
2020-03-27 13:59:38 -07:00
import { FormattedMessage } from 'react-intl' ;
2022-03-21 11:09:01 -07:00
import { connect } from 'react-redux' ;
2022-01-10 14:25:06 -08:00
2022-04-19 14:30:10 -07:00
import { getSoapboxConfig } from 'soapbox/actions/soapbox' ;
2022-11-15 12:48:54 -08:00
import * as BuildConfig from 'soapbox/build-config' ;
2022-11-25 09:04:11 -08:00
import { HStack , Text , Stack } from 'soapbox/components/ui' ;
2022-01-10 14:17:52 -08:00
import { captureException } from 'soapbox/monitoring' ;
2022-11-15 12:42:22 -08:00
import KVStore from 'soapbox/storage/kv-store' ;
2022-02-23 09:42:04 -08:00
import sourceCode from 'soapbox/utils/code' ;
2022-11-03 16:48:10 -07:00
import { unregisterSw } from 'soapbox/utils/sw' ;
2020-03-27 13:59:38 -07:00
2022-07-22 10:30:16 -07:00
import SiteLogo from './site-logo' ;
2022-04-19 14:30:10 -07:00
import type { RootState } from 'soapbox/store' ;
2022-03-21 11:09:01 -07:00
2022-04-19 14:30:10 -07:00
const goHome = ( ) = > location . href = '/' ;
const mapStateToProps = ( state : RootState ) = > {
const { links , logo } = getSoapboxConfig ( state ) ;
2022-03-21 11:09:01 -07:00
return {
2022-03-21 11:33:10 -07:00
siteTitle : state.instance.title ,
2022-04-19 14:30:10 -07:00
logo ,
links ,
2022-03-21 11:09:01 -07:00
} ;
} ;
2022-04-19 14:30:10 -07:00
type Props = ReturnType < typeof mapStateToProps > ;
2020-03-27 13:59:38 -07:00
2022-04-19 14:30:10 -07:00
type State = {
hasError : boolean ,
error : any ,
componentStack : any ,
browser? : Bowser.Parser.Parser ,
}
class ErrorBoundary extends React . PureComponent < Props , State > {
2020-03-27 13:59:38 -07:00
2022-04-19 14:30:10 -07:00
state : State = {
2020-03-27 13:59:38 -07:00
hasError : false ,
2022-04-19 14:30:10 -07:00
error : undefined ,
2020-03-27 13:59:38 -07:00
componentStack : undefined ,
2022-04-19 14:30:10 -07:00
browser : undefined ,
2020-03-27 13:59:38 -07:00
}
2022-04-19 14:30:10 -07:00
textarea : HTMLTextAreaElement | null = null ;
componentDidCatch ( error : any , info : any ) : void {
2022-08-25 11:49:08 -07:00
captureException ( error , {
tags : {
// Allow page crashes to be easily searched in Sentry.
ErrorBoundary : 'yes' ,
} ,
} ) ;
2021-09-08 13:45:44 -07:00
2020-03-27 13:59:38 -07:00
this . setState ( {
hasError : true ,
2021-07-05 16:08:04 -07:00
error ,
2020-03-27 13:59:38 -07:00
componentStack : info && info . componentStack ,
} ) ;
2021-09-11 16:29:43 -07:00
import ( /* webpackChunkName: "error" */ 'bowser' )
. then ( ( { default : Bowser } ) = > {
this . setState ( {
browser : Bowser.getParser ( window . navigator . userAgent ) ,
} ) ;
} )
. catch ( ( ) = > { } ) ;
2020-03-27 13:59:38 -07:00
}
2022-04-19 14:30:10 -07:00
setTextareaRef : React.RefCallback < HTMLTextAreaElement > = c = > {
2021-07-05 16:08:04 -07:00
this . textarea = c ;
}
2022-04-19 14:30:10 -07:00
handleCopy : React.MouseEventHandler = ( ) = > {
2021-07-05 16:08:04 -07:00
if ( ! this . textarea ) return ;
this . textarea . select ( ) ;
this . textarea . setSelectionRange ( 0 , 99999 ) ;
document . execCommand ( 'copy' ) ;
}
2022-04-19 14:30:10 -07:00
getErrorText = ( ) : string = > {
2021-07-05 16:08:04 -07:00
const { error , componentStack } = this . state ;
return error + componentStack ;
}
2022-04-19 14:30:10 -07:00
clearCookies : React.MouseEventHandler = ( e ) = > {
2021-04-22 11:57:47 -07:00
localStorage . clear ( ) ;
sessionStorage . clear ( ) ;
2022-04-20 08:46:49 -07:00
KVStore . clear ( ) ;
2022-04-19 14:30:10 -07:00
if ( 'serviceWorker' in navigator ) {
e . preventDefault ( ) ;
unregisterSw ( ) . then ( goHome ) . catch ( goHome ) ;
}
2021-04-22 11:57:47 -07:00
}
2020-03-27 13:59:38 -07:00
render() {
2021-09-11 16:29:43 -07:00
const { browser , hasError } = this . state ;
2022-07-22 10:30:16 -07:00
const { children , links } = this . props ;
2020-03-27 13:59:38 -07:00
if ( ! hasError ) {
2022-03-21 11:09:01 -07:00
return children ;
2020-03-27 13:59:38 -07:00
}
2022-04-19 14:30:10 -07:00
const isProduction = BuildConfig . NODE_ENV === 'production' ;
2022-03-21 11:09:01 -07:00
2021-07-05 16:08:04 -07:00
const errorText = this . getErrorText ( ) ;
2020-03-27 13:59:38 -07:00
return (
2022-07-22 10:30:16 -07:00
< div className = 'h-screen pt-16 pb-12 flex flex-col bg-white dark:bg-primary-900' >
2022-03-21 11:09:01 -07:00
< main className = 'flex-grow flex flex-col justify-center max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8' >
< div className = 'flex-shrink-0 flex justify-center' >
< a href = '/' className = 'inline-flex' >
2022-07-22 10:30:16 -07:00
< SiteLogo alt = 'Logo' className = 'h-12 w-auto cursor-pointer' / >
2021-10-10 00:48:35 -07:00
< / a >
< / div >
2022-03-21 11:09:01 -07:00
< div className = 'py-8' >
< div className = 'text-center max-w-xl mx-auto space-y-2' >
2022-07-22 10:30:16 -07:00
< h1 className = 'text-3xl font-extrabold text-gray-900 dark:text-gray-500 tracking-tight sm:text-4xl' >
2022-03-21 11:09:01 -07:00
< FormattedMessage id = 'alert.unexpected.message' defaultMessage = 'Something went wrong.' / >
< / h1 >
2022-07-22 10:30:16 -07:00
< p className = 'text-lg text-gray-700 dark:text-gray-600' >
2022-04-19 14:30:10 -07:00
< FormattedMessage
id = 'alert.unexpected.body'
defaultMessage = "We're sorry for the interruption. If the problem persists, please reach out to our support team. You may also try to {clearCookies} (this will log you out)."
2022-07-22 10:30:16 -07:00
values = { {
clearCookies : (
< a href = '/' onClick = { this . clearCookies } className = 'text-primary-600 dark:text-accent-blue hover:underline' >
< FormattedMessage
id = 'alert.unexpected.clear_cookies'
defaultMessage = 'clear cookies and browser data'
/ >
< / a >
) ,
} }
2022-04-19 14:30:10 -07:00
/ >
2022-03-21 11:09:01 -07:00
< / p >
< Text theme = 'muted' >
< Text weight = 'medium' tag = 'span' theme = 'muted' > { sourceCode . displayName } : < / Text >
{ ' ' } { sourceCode . version }
< / Text >
< div className = 'mt-10' >
2022-07-22 10:30:16 -07:00
< a href = '/' className = 'text-base font-medium text-primary-600 dark:text-accent-blue hover:underline' >
2022-03-21 11:09:01 -07:00
< FormattedMessage id = 'alert.unexpected.return_home' defaultMessage = 'Return Home' / >
< span aria-hidden = 'true' > & rarr ; < / span >
< / a >
< / div >
< / div >
{ ! isProduction && (
< div className = 'py-16 max-w-lg mx-auto space-y-4' >
{ errorText && (
< textarea
ref = { this . setTextareaRef }
2022-07-22 10:30:16 -07:00
className = 'h-48 p-4 shadow-sm bg-gray-100 text-gray-900 dark:text-gray-100 dark:bg-gray-800 focus:ring-2 focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-300 dark:border-gray-700 rounded-md font-mono'
2022-03-21 11:09:01 -07:00
value = { errorText }
onClick = { this . handleCopy }
readOnly
/ >
) }
{ browser && (
< Stack >
2022-04-19 14:30:10 -07:00
< Text weight = 'semibold' > < FormattedMessage id = 'alert.unexpected.browser' defaultMessage = 'Browser' / > < / Text >
2022-03-21 11:09:01 -07:00
< Text theme = 'muted' > { browser . getBrowserName ( ) } { browser . getBrowserVersion ( ) } < / Text >
< / Stack >
) }
< / div >
) }
< / div >
< / main >
< footer className = 'flex-shrink-0 max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8' >
2022-11-25 09:04:11 -08:00
< HStack justifyContent = 'center' space = { 4 } element = 'nav' >
2022-04-19 14:30:10 -07:00
{ links . get ( 'status' ) && (
2022-03-21 11:09:01 -07:00
< >
2022-07-22 10:30:16 -07:00
< a href = { links . get ( 'status' ) } className = 'text-sm font-medium text-gray-700 dark:text-gray-600 hover:underline' >
2022-04-19 14:30:10 -07:00
< FormattedMessage id = 'alert.unexpected.links.status' defaultMessage = 'Status' / >
2022-03-21 11:09:01 -07:00
< / a >
< / >
) }
2022-04-19 14:30:10 -07:00
{ links . get ( 'help' ) && (
2022-03-21 11:09:01 -07:00
< >
< span className = 'inline-block border-l border-gray-300' aria-hidden = 'true' / >
2022-07-22 10:30:16 -07:00
< a href = { links . get ( 'help' ) } className = 'text-sm font-medium text-gray-700 dark:text-gray-600 hover:underline' >
2022-04-19 14:30:10 -07:00
< FormattedMessage id = 'alert.unexpected.links.help' defaultMessage = 'Help Center' / >
2022-03-21 11:09:01 -07:00
< / a >
< / >
) }
2022-04-19 14:30:10 -07:00
{ links . get ( 'support' ) && (
2022-03-21 11:09:01 -07:00
< >
< span className = 'inline-block border-l border-gray-300' aria-hidden = 'true' / >
2022-07-22 10:30:16 -07:00
< a href = { links . get ( 'support' ) } className = 'text-sm font-medium text-gray-700 dark:text-gray-600 hover:underline' >
2022-04-19 14:30:10 -07:00
< FormattedMessage id = 'alert.unexpected.links.support' defaultMessage = 'Support' / >
2022-03-21 11:09:01 -07:00
< / a >
< / >
) }
2022-11-25 09:04:11 -08:00
< / HStack >
2022-03-21 11:09:01 -07:00
< / footer >
2020-03-27 13:59:38 -07:00
< / div >
) ;
}
}
2022-03-21 11:09:01 -07:00
2022-04-19 14:30:10 -07:00
export default connect ( mapStateToProps ) ( ErrorBoundary as any ) ;