|
@@ -1,24 +1,22 @@
|
|
|
-import {Component} from 'react';
|
|
|
+import styled from '@emotion/styled';
|
|
|
import * as Sentry from '@sentry/react';
|
|
|
-import classNames from 'classnames';
|
|
|
|
|
|
import Button from 'sentry/components/button';
|
|
|
+import ButtonBar from 'sentry/components/buttonBar';
|
|
|
import {IconFlag} from 'sentry/icons';
|
|
|
import {t} from 'sentry/locale';
|
|
|
+import space from 'sentry/styles/space';
|
|
|
|
|
|
-type DefaultProps = {
|
|
|
- /**
|
|
|
- * Hide support links in footer of error message
|
|
|
- */
|
|
|
- hideSupportLinks: boolean;
|
|
|
-};
|
|
|
-
|
|
|
-type Props = DefaultProps & {
|
|
|
+type Props = {
|
|
|
/**
|
|
|
* Error heading
|
|
|
*/
|
|
|
heading: React.ReactNode;
|
|
|
className?: string;
|
|
|
+ /**
|
|
|
+ * Hide support links in footer of error message
|
|
|
+ */
|
|
|
+ hideSupportLinks?: boolean;
|
|
|
/**
|
|
|
* Detailed error explanation
|
|
|
*/
|
|
@@ -26,7 +24,7 @@ type Props = DefaultProps & {
|
|
|
/**
|
|
|
* Retry callback
|
|
|
*/
|
|
|
- onRetry?: (e: React.MouseEvent<HTMLAnchorElement>) => void;
|
|
|
+ onRetry?: (e: React.MouseEvent) => void;
|
|
|
};
|
|
|
|
|
|
function openFeedback(e: React.MouseEvent) {
|
|
@@ -34,66 +32,64 @@ function openFeedback(e: React.MouseEvent) {
|
|
|
Sentry.showReportDialog();
|
|
|
}
|
|
|
|
|
|
-class DetailedError extends Component<Props> {
|
|
|
- static defaultProps: DefaultProps = {
|
|
|
- hideSupportLinks: false,
|
|
|
- };
|
|
|
-
|
|
|
- componentDidMount() {
|
|
|
- // XXX(epurkhiser): Why is this here?
|
|
|
- this.forceUpdateTimeout = window.setTimeout(() => this.forceUpdate(), 100);
|
|
|
- }
|
|
|
-
|
|
|
- componentWillUnmount() {
|
|
|
- window.clearTimeout(this.forceUpdateTimeout);
|
|
|
- }
|
|
|
-
|
|
|
- forceUpdateTimeout: number | undefined = undefined;
|
|
|
-
|
|
|
- render() {
|
|
|
- const {className, heading, message, onRetry, hideSupportLinks} = this.props;
|
|
|
- const cx = classNames('detailed-error', className);
|
|
|
-
|
|
|
- const showFooter = !!onRetry || !hideSupportLinks;
|
|
|
-
|
|
|
- return (
|
|
|
- <div className={cx}>
|
|
|
- <div className="detailed-error-icon">
|
|
|
- <IconFlag size="lg" />
|
|
|
- </div>
|
|
|
- <div className="detailed-error-content">
|
|
|
- <h4>{heading}</h4>
|
|
|
-
|
|
|
- <div className="detailed-error-content-body">{message}</div>
|
|
|
-
|
|
|
- {showFooter && (
|
|
|
- <div className="detailed-error-content-footer">
|
|
|
- <div>
|
|
|
- {onRetry && (
|
|
|
- <a onClick={onRetry} className="btn btn-default">
|
|
|
- {t('Retry')}
|
|
|
- </a>
|
|
|
- )}
|
|
|
- </div>
|
|
|
-
|
|
|
- {!hideSupportLinks && (
|
|
|
- <div className="detailed-error-support-links">
|
|
|
- {Sentry.lastEventId() && (
|
|
|
- <Button priority="link" onClick={openFeedback}>
|
|
|
- {t('Fill out a report')}
|
|
|
- </Button>
|
|
|
- )}
|
|
|
- <a href="https://status.sentry.io/">{t('Service status')}</a>
|
|
|
-
|
|
|
- <a href="https://sentry.io/support/">{t('Contact support')}</a>
|
|
|
- </div>
|
|
|
+function DetailedError({className, heading, message, onRetry, hideSupportLinks}: Props) {
|
|
|
+ const showFooter = !!onRetry || !hideSupportLinks;
|
|
|
+ const hasLastEvent = !!Sentry.lastEventId();
|
|
|
+
|
|
|
+ return (
|
|
|
+ <Wrapper className={className}>
|
|
|
+ <ErrorHeading>
|
|
|
+ <IconFlag size="md" color="red300" />
|
|
|
+ {heading}
|
|
|
+ </ErrorHeading>
|
|
|
+
|
|
|
+ {message}
|
|
|
+
|
|
|
+ {showFooter && (
|
|
|
+ <ErrorFooter>
|
|
|
+ <div>{onRetry && <Button onClick={onRetry}>{t('Retry')}</Button>}</div>
|
|
|
+
|
|
|
+ {!hideSupportLinks && (
|
|
|
+ <ButtonBar gap={1.5}>
|
|
|
+ {hasLastEvent && (
|
|
|
+ <Button priority="link" onClick={openFeedback}>
|
|
|
+ {t('Fill out a report')}
|
|
|
+ </Button>
|
|
|
)}
|
|
|
- </div>
|
|
|
+ <Button priority="link" external href="https://status.sentry.io/">
|
|
|
+ {t('Service status')}
|
|
|
+ </Button>
|
|
|
+ <Button priority="link" external href="https://sentry.io/support/">
|
|
|
+ {t('Contact support')}
|
|
|
+ </Button>
|
|
|
+ </ButtonBar>
|
|
|
)}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- );
|
|
|
- }
|
|
|
+ </ErrorFooter>
|
|
|
+ )}
|
|
|
+ </Wrapper>
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
+const Wrapper = styled('div')`
|
|
|
+ margin: ${space(2)} auto 0 auto;
|
|
|
+ padding: ${space(2)};
|
|
|
+ width: max-content;
|
|
|
+`;
|
|
|
+
|
|
|
+const ErrorHeading = styled('h4')`
|
|
|
+ display: flex;
|
|
|
+ gap: ${space(1.5)};
|
|
|
+ align-items: center;
|
|
|
+ margin-left: calc(-1 * (${p => p.theme.iconSizes.md} + ${space(1.5)}));
|
|
|
+`;
|
|
|
+
|
|
|
+const ErrorFooter = styled('div')`
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-top: ${space(2)};
|
|
|
+ border-top: 1px solid ${p => p.theme.innerBorder};
|
|
|
+ padding-top: ${space(2)};
|
|
|
+`;
|
|
|
+
|
|
|
export default DetailedError;
|