Commit e370a1f5 authored by Nick Lanam's avatar Nick Lanam
Browse files

Less scary messaging for auth errors

Summary:
Displays the Pixienaut being chased by an octopus instead of a big red triangle + exclamation point. Also adds an option to render with the Pixienaut on the toilet, in an "oh no" pose. Messaging for sign in, log in, and general auth errors made more friendly and given a call to action (navigating) button.

The toilet pose is not in use yet. The goal going forward is to use the octopus for recoverable errors and the toilet for fatal errors.

The octopus box:
{F104225}

The toilet box (not in use at this time, but fatal errors are welcome to use it going forward):
{F104226}

Test Plan: To trigger the log in error, try to log in with a Google account that isn't registered with us. To trigger the sign up error, try to register an account that is already registered.

Reviewers: nserrino, michelle, #engineering, vihang

Reviewed By: #engineering, vihang

Subscribers: vihang

JIRA Issues: PC-575

Differential Revision: https://phab.corp.pixielabs.ai/D6769

GitOrigin-RevId: 8c4e9cd3163f10e53a3ebafd82dd389ed0930cdc
parent ad0a44b3
Showing with 2233 additions and 212 deletions
+2233 -212
......@@ -37,6 +37,7 @@
"@types/segment-analytics": "^0.0.32",
"@typescript-eslint/eslint-plugin": "^2.30.0",
"@typescript-eslint/parser": "^2.30.0",
"@webpack-cli/serve": "^1.1.0",
"apollo-utilities": "^1.3.4",
"archiver": "^5.0.2",
"awesome-typescript-loader": "^5.2.0",
......
......@@ -63,6 +63,7 @@
"@types/node": "^14.0.14",
"@types/react": "^16.9.11",
"@types/react-dom": "^16.9.4",
"@types/react-router-dom": "^5.1.6",
"@types/react-virtualized": "^9.21.8",
"@typescript-eslint/eslint-plugin": "^2.30.0",
"@typescript-eslint/parser": "^2.30.0",
......
......@@ -24,15 +24,15 @@ export const Completed = () => (
export const Error = () => (
<AuthMessageBox
error
error='recoverable'
title='Auth Failed'
message='Login to this org is not allowed.'
message='Check your spelling and try again.'
/>
);
export const ErrorDetails = () => (
<AuthMessageBox
error
error='fatal'
errorDetails='Internal error: bad things happened'
title='Auth Failed'
message='Login to this org is not allowed.'
......
......@@ -7,66 +7,79 @@ import {
withStyles,
WithStyles,
} from '@material-ui/core';
import { AuthErrorSvg } from './auth-error';
import { PixienautBox } from './pixienaut-box';
import { CodeRenderer } from 'components/code-renderer/code-renderer';
import { PixienautBox, PixienautBoxProps } from './pixienaut-box';
const styles = ({ palette, spacing }: Theme) =>
createStyles({
root: {
backgroundColor: fade(palette.foreground.grey3, 0.8),
paddingLeft: spacing(6),
paddingRight: spacing(6),
paddingTop: spacing(10),
paddingBottom: spacing(10),
boxShadow: `0px ${spacing(0.25)}px ${spacing(2)}px rgba(0, 0, 0, 0.6)`,
borderRadius: spacing(3),
},
title: {
color: palette.foreground.two,
},
message: {
color: palette.foreground.one,
marginTop: spacing(5.5),
marginBottom: spacing(4),
},
errorDetails: {
color: palette.foreground.grey4,
marginTop: spacing(4),
},
extraPadding: {
height: spacing(6),
},
});
const styles = ({ palette, spacing }: Theme) => createStyles({
root: {
backgroundColor: fade(palette.foreground.grey3, 0.8),
paddingLeft: spacing(6),
paddingRight: spacing(6),
paddingTop: spacing(10),
paddingBottom: spacing(10),
boxShadow: `0px ${spacing(0.25)}px ${spacing(2)}px rgba(0, 0, 0, 0.6)`,
borderRadius: spacing(3),
},
title: {
color: palette.foreground.two,
},
message: {
color: palette.foreground.one,
marginTop: spacing(5.5),
marginBottom: spacing(4),
},
errorDetails: {
color: palette.foreground.grey4,
marginTop: spacing(4),
},
extraPadding: {
height: spacing(6),
},
});
export interface AuthMessageBoxProps extends WithStyles<typeof styles> {
error?: boolean;
error?: 'recoverable'|'fatal';
title: string;
message: string;
errorDetails?: string;
code?: string;
cta?: React.ReactNode;
}
export const AuthMessageBox = withStyles(styles)(
(props: AuthMessageBoxProps) => {
const { error, errorDetails, title, message, code, classes } = props;
const errorImage = <AuthErrorSvg />;
return (
<PixienautBox overrideImage={error ? errorImage : undefined}>
<Typography variant='h1' className={classes.title}>
{title}
</Typography>
<Typography variant='h6' className={classes.message}>
{message}
</Typography>
{code && <CodeRenderer code={code} />}
{error && errorDetails && (
<Typography variant='body1' className={classes.errorDetails}>
{`Details: ${errorDetails}`}
</Typography>
)}
<div className={classes.extraPadding} />
</PixienautBox>
);
export const AuthMessageBox = withStyles(styles)((props: AuthMessageBoxProps) => {
const {
error,
errorDetails,
title,
message,
code,
cta,
classes,
} = props;
let scenario: PixienautBoxProps['image'] = 'balloon';
if (error) {
scenario = error === 'recoverable' ? 'octopus' : 'toilet';
}
);
return (
<PixienautBox image={scenario}>
<Typography variant='h1' className={classes.title}>
{title}
</Typography>
<Typography variant='h6' className={classes.message}>
{message}
</Typography>
{code && (
<CodeRenderer
code={code}
/>
)}
{error && errorDetails && (
<Typography variant='body1' className={classes.errorDetails}>
{`Details: ${errorDetails}`}
</Typography>
)}
{cta}
<div className={classes.extraPadding} />
</PixienautBox>
);
});
import * as React from 'react';
export const PixienautBalloonSvg = (props) => (
/* eslint-disable max-len */
<svg width={129} height={179} viewBox='0 0 129 179' fill='none' {...props}>
<path
fillRule='evenodd'
......@@ -700,4 +701,5 @@ export const PixienautBalloonSvg = (props) => (
</linearGradient>
</defs>
</svg>
/* eslint-enable max-len */
);
......@@ -7,67 +7,110 @@ import {
withStyles,
} from '@material-ui/core';
import { PixienautBalloonSvg } from './pixienaut-balloon';
import { PixienautOctopusSvg } from './pixienaut-octopus';
import { PixienautToiletSvg } from './pixienaut-toilet';
const styles = ({ spacing, palette, breakpoints }: Theme) =>
createStyles({
root: {
backgroundColor: fade(palette.foreground.grey3, 0.8),
paddingLeft: spacing(5),
paddingRight: spacing(5),
paddingTop: spacing(0),
paddingBottom: spacing(1),
boxShadow: `0px ${spacing(0.25)}px ${spacing(2)}px rgba(0, 0, 0, 0.6)`,
borderRadius: spacing(3),
minWidth: '370px',
maxWidth: breakpoints.values.xs,
maxHeight: '550px',
},
splashImageContainer: {
display: 'flex',
justifyContent: 'center',
alignItems: 'flex-start',
padding: spacing(3),
},
pixienautContainer: {
// The Pixienaut is still kinda close to Earth (this component's content)
// This + splash container padding + relative Pixienaut position = spacing(4) under its foot
marginBottom: spacing(-9),
marginTop: spacing(-2),
},
pixienautImage: {
// The balloons raise the Pixienaut up out of the container
// (and correcting for unusual image dimensions)
position: 'relative',
bottom: spacing(8.5),
left: spacing(2),
padding: 0,
},
content: {
display: 'flex',
flexFlow: 'column nowrap',
alignItems: 'center',
justifyContent: 'flex-start',
width: '100%',
textAlign: 'center',
},
});
const styles = ({ spacing, palette, breakpoints }: Theme) => createStyles({
root: {
backgroundColor: fade(palette.foreground.grey3, 0.8),
paddingLeft: spacing(5),
paddingRight: spacing(5),
paddingTop: spacing(0),
paddingBottom: spacing(1),
boxShadow: `0px ${spacing(0.25)}px ${spacing(2)}px rgba(0, 0, 0, 0.6)`,
borderRadius: spacing(3),
minWidth: '370px',
maxWidth: breakpoints.values.xs,
maxHeight: '550px',
},
splashImageContainer: {
display: 'flex',
justifyContent: 'center',
alignItems: 'flex-start',
padding: spacing(3),
},
pixienautBalloonContainer: {
// The Pixienaut is still kinda close to Earth (this component's content)
// This + splash container padding + relative Pixienaut position = spacing(4) under its foot
marginBottom: spacing(-9),
marginTop: spacing(-2),
},
pixienautBalloonImage: {
// The balloons raise the Pixienaut up out of the container
// (and correcting for unusual image dimensions)
position: 'relative',
bottom: spacing(8.5),
left: spacing(2),
padding: 0,
},
pixienautOctopusContainer: {
width: '100%',
marginBottom: spacing(-13),
},
pixienautOctopusImage: {
maxWidth: '100%',
height: 'auto',
position: 'relative',
bottom: spacing(13),
},
pixienautToiletContainer: {
width: '100%',
textAlign: 'center',
marginBottom: spacing(-15),
},
pixienautToiletImage: {
maxWidth: '60%',
height: 'auto',
position: 'relative',
bottom: spacing(15),
},
content: {
display: 'flex',
flexFlow: 'column nowrap',
alignItems: 'center',
justifyContent: 'flex-start',
width: '100%',
textAlign: 'center',
},
});
export interface PixienautBoxProps extends WithStyles<typeof styles> {
overrideImage?: React.ReactNode;
children?: React.ReactNode;
/**
* What is the Pixienaut doing? Options:
* - Being lifted by balloons (default)
* - Running from a space octopus (use for recoverable errors)
* - Using the toilet with an "oh no" pose (use for fatal errors)
*/
image?: 'balloon'|'octopus'|'toilet';
}
export const PixienautBox = withStyles(styles)(
({ classes, children, overrideImage }: PixienautBoxProps) => (
export const PixienautBox = withStyles(styles)(({ classes, children, image = 'balloon' }: PixienautBoxProps) => {
const pixienautScenarios = {
balloon: (
<div className={classes.pixienautBalloonContainer}>
<PixienautBalloonSvg className={classes.pixienautBalloonImage} />
</div>
),
octopus: (
<div className={classes.pixienautOctopusContainer}>
<PixienautOctopusSvg className={classes.pixienautOctopusImage} />
</div>
),
toilet: (
<div className={classes.pixienautToiletContainer}>
<PixienautToiletSvg className={classes.pixienautToiletImage} />
</div>
),
};
return (
<div className={classes.root}>
<div className={classes.splashImageContainer}>
{overrideImage ?? (
<div className={classes.pixienautContainer}>
<PixienautBalloonSvg className={classes.pixienautImage} />
</div>
)}
{pixienautScenarios[image]}
</div>
<div className={classes.content}>
{children}
</div>
<div className={classes.content}>{children}</div>
</div>
)
);
);
});
This diff is collapsed.
This diff is collapsed.
......@@ -6,6 +6,10 @@ import Axios, { AxiosError } from 'axios';
import * as RedirectUtils from 'utils/redirect-utils';
import { isValidAnalytics } from 'utils/env';
import { AuthMessageBox } from 'pixie-components';
import { Link } from 'react-router-dom';
import {
Button, createStyles, makeStyles, Theme,
} from '@material-ui/core';
import { BasePage } from './base';
import { AuthCallbackMode } from './utils';
......@@ -47,6 +51,15 @@ const CLICodeBox = ({ code }) => (
/>
);
const useStyles = makeStyles((theme: Theme) => createStyles({
ctaGutter: {
marginTop: theme.spacing(3),
paddingTop: theme.spacing(3),
borderTop: `1px solid ${theme.palette.foreground.grey1}`,
width: '80%',
},
}));
/**
* This is the main component to handle the callback from auth.
*
......@@ -55,6 +68,7 @@ const CLICodeBox = ({ code }) => (
*/
export const AuthCallbackPage = () => {
const [config, setConfig] = React.useState<CallbackConfig>(null);
const classes = useStyles();
React.useEffect(() => {
const setErr = (errType: ErrorType, errMsg: string) => {
......@@ -199,34 +213,43 @@ export const AuthCallbackPage = () => {
}, []);
const renderError = () => {
let title: string;
let errorDetails: string;
let message: string;
if (config.signup) {
title = 'Failed to Signup user';
if (config.err.errorType === 'internal') {
message = 'Sorry we hit a snag failed to create an account. Please try again later.';
errorDetails = config.err.errMessage;
const title = config.signup ? 'Failed to Sign Up' : 'Failed to Log In';
const errorDetails = config.err.errorType === 'internal' ? config.err.errMessage : undefined;
let ctaMessage: string;
let ctaDestination: string;
let errorMessage: string;
if (config.err.errorType === 'internal') {
if (config.signup) {
errorMessage = 'We hit a snag in creating an account. Please try again later.';
ctaMessage = 'Back to Sign Up';
ctaDestination = '/auth/signup';
} else {
message = config.err.errMessage;
errorMessage = 'We hit a snag trying to authenticate you. Please try again later.';
ctaMessage = 'Back to Log In';
ctaDestination = '/auth/login';
}
} else {
// Login.
title = 'Failed to authenticate';
if (config.err.errorType === 'internal') {
message = 'Sorry we hit a snag failed to authenticate you. Please try again later.';
errorDetails = config.err.errMessage;
} else {
message = config.err.errMessage;
}
errorMessage = config.err.errMessage;
ctaMessage = 'Go Back';
ctaDestination = '/';
}
const cta = (
<div className={classes.ctaGutter}>
<Link to={ctaDestination} component={Button} color='primary' variant='contained'>
{ ctaMessage }
</Link>
</div>
);
return (
<AuthMessageBox
error
error='recoverable'
title={title}
message={message}
message={errorMessage}
errorDetails={errorDetails}
cta={cta}
/>
);
};
......
This diff is collapsed.
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment