import React, { useState } from 'react'; import Block from './common/Block'; import Button from './common/Button'; import ExternalLink from './common/ExternalLink'; import LinkButton from './common/LinkButton'; import Modal from './common/Modal'; import Paragraph from './common/Paragraph'; import Stack from './common/Stack'; import Text from './common/Text'; import View from './common/View'; import { Checkbox } from './forms'; type AppError = Error & { type?: string; IDBFailure?: boolean; SharedArrayBufferMissing?: boolean; }; type FatalErrorProps = { buttonText: string; error: Error | AppError; }; type RenderSimpleProps = { error: Error | AppError; }; function RenderSimple({ error }: RenderSimpleProps) { let msg; if ('IDBFailure' in error && error.IDBFailure) { // IndexedDB wasn't able to open the database msg = ( <Text> Your browser doesn’t support IndexedDB in this environment, a feature that Actual requires to run. This might happen if you are in private browsing mode. Please try a different browser or turn off private browsing. </Text> ); } else if ( 'SharedArrayBufferMissing' in error && error.SharedArrayBufferMissing ) { // SharedArrayBuffer isn't available msg = ( <Text> Actual requires access to <code>SharedArrayBuffer</code> in order to function properly. If you’re seeing this error, either your browser does not support <code>SharedArrayBuffer</code>, or your server is not sending the appropriate headers, or you are not using HTTPS. See{' '} <ExternalLink linkColor="muted" to="https://actualbudget.org/docs/troubleshooting/shared-array-buffer" > our troubleshooting documentation </ExternalLink>{' '} to learn more. <SharedArrayBufferOverride /> </Text> ); } else { // This indicates the backend failed to initialize. Show the // user something at least so they aren't looking at a blank // screen msg = ( <Text> There was a problem loading the app in this browser version. If this continues to be a problem, you can{' '} <ExternalLink linkColor="muted" to="https://github.com/actualbudget/releases" > download the desktop app </ExternalLink> . </Text> ); } return ( <Stack style={{ paddingBottom: 15, lineHeight: '1.5em', fontSize: 15, }} > <Text>{msg}</Text> <Text> Please get{' '} <ExternalLink linkColor="muted" to="https://actualbudget.org/contact"> in touch </ExternalLink>{' '} for support </Text> </Stack> ); } function RenderUIError() { return ( <> <Paragraph>There was an unrecoverable error in the UI. Sorry!</Paragraph> <Paragraph> If this error persists, please get{' '} <ExternalLink to="https://actualbudget.org/contact"> in touch </ExternalLink>{' '} so it can be investigated. </Paragraph> </> ); } function SharedArrayBufferOverride() { let [expanded, setExpanded] = useState(false); let [understand, setUnderstand] = useState(false); return expanded ? ( <> <Paragraph style={{ marginTop: 10 }}> Actual uses <code>SharedArrayBuffer</code> to allow usage from multiple tabs at once and to ensure correct behavior when switching files. While it can run without access to <code>SharedArrayBuffer</code>, you may encounter data loss or notice multiple budget files being merged with each other. </Paragraph> <label style={{ display: 'flex', alignItems: 'center', marginBottom: 10 }} > <Checkbox checked={understand} onChange={_ => setUnderstand(!understand)} />{' '} I understand the risks, run Actual in the unsupported fallback mode </label> <Button disabled={!understand} onClick={() => { window.localStorage.setItem('SharedArrayBufferOverride', 'true'); window.location.reload(); }} > Open Actual </Button> </> ) : ( <LinkButton onClick={() => setExpanded(true)} style={{ marginLeft: 5 }}> Advanced options </LinkButton> ); } function FatalError({ buttonText, error }: FatalErrorProps) { let [showError, setShowError] = useState(false); const showSimpleRender = 'type' in error && error.type === 'app-init-failure'; return ( <Modal isCurrent={true} showClose={false} title="Fatal Error"> <View style={{ maxWidth: 500, }} > {showSimpleRender ? <RenderSimple error={error} /> : <RenderUIError />} <Paragraph> <Button onClick={() => window.Actual.relaunch()}>{buttonText}</Button> </Paragraph> <Paragraph isLast={true} style={{ fontSize: 11 }}> <LinkButton onClick={() => setShowError(true)}>Show Error</LinkButton> {showError && ( <Block style={{ marginTop: 5, height: 100, overflow: 'auto', }} > {error.stack} </Block> )} </Paragraph> </View> </Modal> ); } export default FatalError;