diff --git a/js/views/reusable/LinkButton.jsx b/js/views/reusable/LinkButton.jsx index 2c33f1b4..e08ef309 100644 --- a/js/views/reusable/LinkButton.jsx +++ b/js/views/reusable/LinkButton.jsx @@ -13,45 +13,52 @@ class LinkButton extends React.Component { onClick: PropTypes.func, color: PropTypes.string, size: PropTypes.string, + target: PropTypes.string, + } + + static defaultProps = { + target: '_blank', } triggerLink = () => { - if (this.props.href.split(':')[0] === 'mailto') { - window.location = this.props.href + const { href } = this.props + if (href.split(':')[0] === 'mailto') { + window.location = href } else { - window.open(this.props.href) + window.open(href) } } render() { + const { href, target, size, color, label, onClick } = this.props let wrapperStyle = null let textStyle = null - if (this.props.size === 'small') { + if (size === 'small') { wrapperStyle = - this.props.color === 'secondary' + color === 'secondary' ? [styles.wrapper, styles.wrapperSecondary, styles.wrapperSmall] : [styles.wrapper, styles.wrapperSmall] textStyle = - this.props.color === 'secondary' + color === 'secondary' ? [styles.text, styles.textSecondary, styles.textSmall] : [styles.text, styles.textSmall] } else { wrapperStyle = - this.props.color === 'secondary' + color === 'secondary' ? [styles.wrapper, styles.wrapperSecondary] : styles.wrapper textStyle = - this.props.color === 'secondary' + color === 'secondary' ? [styles.text, styles.textSecondary] : styles.text } const inner = ( - {this.props.label} + {label} ) - if (this.props.href && iOS.detect()) { + if (href && iOS.detect()) { return ( ) } - if (this.props.href) { + if (href) { return ( {inner} ) } - return ( - {inner} - ) + return {inner} } } styles = StyleSheet.create({ diff --git a/js/views/shell/Content.jsx b/js/views/shell/Content.jsx index b3adcec2..f7e8ea25 100644 --- a/js/views/shell/Content.jsx +++ b/js/views/shell/Content.jsx @@ -4,6 +4,7 @@ import { View, StyleSheet } from 'react-native' import { withRouter, Route } from 'react-router-dom' import Switch from './Switch.jsx' +import { ErrorBoundary } from './ErrorBoundary.jsx' import Events from '../../stores/Events' import Station from '../station/Station.jsx' @@ -75,27 +76,40 @@ class Content extends React.Component { } onLayout={this.triggerLayout} > - - - - - - - - - - - - - + + + + { + throw new Error('Intentional error') + }} + /> + + + + + + + + + + + + ) } diff --git a/js/views/shell/ErrorBoundary.jsx b/js/views/shell/ErrorBoundary.jsx new file mode 100644 index 00000000..4e9b6269 --- /dev/null +++ b/js/views/shell/ErrorBoundary.jsx @@ -0,0 +1,74 @@ +import React, { Component } from 'react' +import { View, Text, StyleSheet } from 'react-native' + +import { vars } from '../../styles.js' +import Wrapper from './Wrapper.jsx' +import Header from '../reusable/Header.jsx' +import LinkButton from '../reusable/LinkButton.jsx' + +let styles + +export class ErrorBoundary extends Component { + constructor(props) { + super(props) + this.state = { hasError: false } + } + + static getDerivedStateFromError() { + return { hasError: true } + } + + componentDidCatch(error, errorInfo) { + // TODO: Log error to an error reporting service + console.error(error, errorInfo) + } + + render() { + const { hasError } = this.state + if (hasError) { + // You can render any custom fallback UI + return ( + + +
+ + + There was an unexpected error. You can either try reload this + page, or return home. + + + + + + + ) + } + + const { children } = this.props + return children + } +} + +const { padding, defaultFontSize, fontFamily } = vars +styles = StyleSheet.create({ + wrapper: { + flex: 1, + }, + error: { + padding, + }, + errorMessage: { + fontSize: defaultFontSize, + fontFamily, + marginBottom: padding, + }, +})