[next] Auth Popup v2 (#2101)

* feat: Implement new Sign In view

* feat: Move forgot + resetPassword to new design

* feat: Implement sign up with new design

* fix: narrow gutter

* test: add unit tests

* test: integration tests

* feat: support show / hide password

* feat: support oauth2 flow

* feat: add views for user completion

* feat: implement oauth2 sign up

* test: fix snapshots

* fix: lint

* fix: get more complete mutation response

* fix: removed array of OIDC integrations

* fix: renamed resolver function

* fix: adapt oidc client implementation

* fix: targetFilter should be stream on signup

* fix: removed unneeded message

* fix: moved password into local profile

* fix: made username optional, removed valid null value

* fix: linting

* fix: respect targetFilter

* feat: support user registration mutations

- Added `setUsername`
- Added `setEmail`
- Added `setPassword`
- Added `permit` to `@auth`
- Added `email` to `User`

* fix: fixed issue with query

* feat: added user password update

* feat: complete sign in mutation

* fix: adapt some rebasing gitches

* test: improve tests

* test: unittest for setting auth token

* fix: failing tests

* test: move most tests from enzyme to react-test-renderer

* fix: remove schema warnings in tests

* test: improve window mock

* test: test different social login configurations

* test: test social logins for sign up

* fix: use htmlFor instead of for

* test: more feature tests

* feat: always go through account completion

* test: feature test account completion

* feat: addtional account completion test

* Update start.ts

* chore: refactor auth token retrieval logic
This commit is contained in:
Kiwi
2018-12-20 22:32:04 +01:00
committed by GitHub
parent 326a10dc5d
commit 065cb4b03a
331 changed files with 12476 additions and 7163 deletions
@@ -1,5 +1,5 @@
import { shallow } from "enzyme";
import React from "react";
import { createRenderer } from "react-test-renderer/shallow";
import { PropTypesOf } from "talk-framework/types";
@@ -12,6 +12,7 @@ it("renders correctly", () => {
gutterEnd: true,
className: "custom",
};
const wrapper = shallow(<AppBar {...props} />);
expect(wrapper).toMatchSnapshot();
const renderer = createRenderer();
renderer.render(<AppBar {...props} />);
expect(renderer.getRenderOutput()).toMatchSnapshot();
});
@@ -1,5 +1,5 @@
import { shallow } from "enzyme";
import React from "react";
import { createRenderer } from "react-test-renderer/shallow";
import { PropTypesOf } from "talk-framework/types";
@@ -11,6 +11,7 @@ it("renders correctly", () => {
className: "custom",
itemGutter: true,
};
const wrapper = shallow(<End {...props} />);
expect(wrapper).toMatchSnapshot();
const renderer = createRenderer();
renderer.render(<End {...props} />);
expect(renderer.getRenderOutput()).toMatchSnapshot();
});
@@ -1,9 +1,10 @@
import { shallow } from "enzyme";
import React from "react";
import { createRenderer } from "react-test-renderer/shallow";
import Divider from "./Divider";
it("renders correctly", () => {
const wrapper = shallow(<Divider />);
expect(wrapper).toMatchSnapshot();
const renderer = createRenderer();
renderer.render(<Divider />);
expect(renderer.getRenderOutput()).toMatchSnapshot();
});
@@ -1,5 +1,5 @@
import { shallow } from "enzyme";
import React from "react";
import { createRenderer } from "react-test-renderer/shallow";
import { PropTypesOf } from "talk-framework/types";
@@ -11,6 +11,7 @@ it("renders correctly", () => {
className: "custom",
itemGutter: true,
};
const wrapper = shallow(<End {...props} />);
expect(wrapper).toMatchSnapshot();
const renderer = createRenderer();
renderer.render(<End {...props} />);
expect(renderer.getRenderOutput()).toMatchSnapshot();
});
@@ -1,5 +1,5 @@
import { shallow } from "enzyme";
import React from "react";
import { createRenderer } from "react-test-renderer/shallow";
import { PropTypesOf } from "talk-framework/types";
@@ -9,6 +9,7 @@ it("renders correctly", () => {
const props: PropTypesOf<typeof Navigation> = {
children: "children",
};
const wrapper = shallow(<Navigation {...props} />);
expect(wrapper).toMatchSnapshot();
const renderer = createRenderer();
renderer.render(<Navigation {...props} />);
expect(renderer.getRenderOutput()).toMatchSnapshot();
});
@@ -17,7 +17,7 @@
.active {
composes: navItemActive from "talk-ui/shared/typography.css";
background-color: var(--palette-brand);
background-color: var(--palette-brand-main);
text-decoration: none;
color: var(--palette-text-light);
}
@@ -1,6 +1,6 @@
import { shallow } from "enzyme";
import { noop } from "lodash";
import React from "react";
import { createRenderer } from "react-test-renderer/shallow";
import { PropTypesOf } from "talk-framework/types";
@@ -14,6 +14,7 @@ it("renders correctly", () => {
active: true,
className: "custom",
};
const wrapper = shallow(<NavigationItem {...props} />);
expect(wrapper).toMatchSnapshot();
const renderer = createRenderer();
renderer.render(<NavigationItem {...props} />);
expect(renderer.getRenderOutput()).toMatchSnapshot();
});
@@ -1,5 +1,5 @@
import { shallow } from "enzyme";
import React from "react";
import { createRenderer } from "react-test-renderer/shallow";
import { PropTypesOf } from "talk-framework/types";
@@ -10,6 +10,7 @@ it("renders correctly", () => {
className: "custom",
size: "lg",
};
const wrapper = shallow(<BrandIcon {...props} />);
expect(wrapper).toMatchSnapshot();
const renderer = createRenderer();
renderer.render(<BrandIcon {...props} />);
expect(renderer.getRenderOutput()).toMatchSnapshot();
});
@@ -1,7 +1,7 @@
.root {
font-weight: var(--font-weight-bold);
font-family: var(--font-family-sans-serif);
color: var(--palette-brand);
color: var(--palette-brand-main);
margin: 0;
}
@@ -1,5 +1,5 @@
import { shallow } from "enzyme";
import React from "react";
import { createRenderer } from "react-test-renderer/shallow";
import { PropTypesOf } from "talk-framework/types";
@@ -11,6 +11,7 @@ it("renders correctly", () => {
className: "custom",
size: "lg",
};
const wrapper = shallow(<BrandName {...props} />);
expect(wrapper).toMatchSnapshot();
const renderer = createRenderer();
renderer.render(<BrandName {...props} />);
expect(renderer.getRenderOutput()).toMatchSnapshot();
});
@@ -1,5 +1,5 @@
import { shallow } from "enzyme";
import React from "react";
import { createRenderer } from "react-test-renderer/shallow";
import { PropTypesOf } from "talk-framework/types";
@@ -9,6 +9,7 @@ it("renders correctly", () => {
const props: PropTypesOf<typeof Logo> = {
className: "custom",
};
const wrapper = shallow(<Logo {...props} />);
expect(wrapper).toMatchSnapshot();
const renderer = createRenderer();
renderer.render(<Logo {...props} />);
expect(renderer.getRenderOutput()).toMatchSnapshot();
});
@@ -24,7 +24,7 @@
}
.fullWidth {
display: block;
display: flex;
width: 100%;
box-sizing: border-box;
}
@@ -57,6 +57,9 @@
&.colorSuccess {
color: var(--palette-success-main);
}
&.colorBrand {
color: var(--palette-brand-main);
}
&:not(.disabled) {
&.colorRegular {
@@ -95,6 +98,15 @@
color: var(--palette-success-lighter);
}
}
&.colorBrand {
&.mouseHover {
color: var(--palette-brand-light);
}
&:active,
&.active {
color: var(--palette-brand-lighter);
}
}
}
}
@@ -112,6 +124,9 @@
&.colorSuccess {
background-color: var(--palette-success-main);
}
&.colorBrand {
background-color: var(--palette-brand-main);
}
&:not(.disabled) {
&.colorRegular {
@@ -150,6 +165,15 @@
background-color: var(--palette-success-lighter);
}
}
&.colorBrand {
&.mouseHover {
background-color: var(--palette-brand-light);
}
&:active,
&.active {
background-color: var(--palette-brand-lighter);
}
}
}
}
@@ -171,6 +195,10 @@
color: var(--palette-success-main);
border: 1px solid currentColor;
}
&.colorBrand {
color: var(--palette-brand-main);
border: 1px solid currentColor;
}
&:not(.disabled) {
&.colorRegular {
@@ -217,6 +245,17 @@
border: 1px solid currentColor;
}
}
&.colorBrand {
&.mouseHover {
color: var(--palette-brand-light);
border: 1px solid currentColor;
}
&:active,
&.active {
color: var(--palette-brand-lighter);
border: 1px solid currentColor;
}
}
}
}
@@ -234,6 +273,9 @@
&.colorSuccess {
color: var(--palette-success-main);
}
&.colorBrand {
color: var(--palette-brand-main);
}
&:not(.disabled) {
&.colorRegular {
@@ -280,6 +322,17 @@
background-color: var(--palette-success-main);
}
}
&.colorBrand {
&.mouseHover {
border: 1px solid currentColor;
}
&:active,
&.active {
border: 1px solid currentColor;
color: var(--palette-text-light);
background-color: var(--palette-brand-main);
}
}
}
}
@@ -302,6 +355,9 @@
&.colorSuccess {
color: var(--palette-success-main);
}
&.colorBrand {
color: var(--palette-brand-main);
}
&:not(.disabled) {
&.colorRegular {
@@ -340,5 +396,14 @@
color: var(--palette-success-lighter);
}
}
&.colorBrand {
&.mouseHover {
color: var(--palette-brand-light);
}
&:active,
&.active {
color: var(--palette-brand-lighter);
}
}
}
}
@@ -26,7 +26,7 @@ interface InnerProps extends BaseButtonProps {
size?: "small" | "regular" | "large";
/** Color of the button */
color?: "regular" | "primary" | "error" | "success";
color?: "regular" | "primary" | "error" | "success" | "brand";
/** Variant of the button */
variant?: "regular" | "filled" | "outlined" | "ghost" | "underlined";
@@ -72,6 +72,7 @@ export class Button extends React.Component<InnerProps> {
[classes.colorPrimary]: color === "primary",
[classes.colorError]: color === "error",
[classes.colorSuccess]: color === "success",
[classes.colorBrand]: color === "brand",
[classes.variantRegular]: variant === "regular",
[classes.variantFilled]: variant === "filled",
[classes.variantOutlined]: variant === "outlined",
@@ -1,9 +1,10 @@
import { shallow } from "enzyme";
import React from "react";
import { createRenderer } from "react-test-renderer/shallow";
import Counter from "./Counter";
it("renders correctly", () => {
const wrapper = shallow(<Counter>20</Counter>);
expect(wrapper).toMatchSnapshot();
const renderer = createRenderer();
renderer.render(<Counter>20</Counter>);
expect(renderer.getRenderOutput()).toMatchSnapshot();
});
@@ -7,6 +7,8 @@ import Typography from "../Typography";
import styles from "./InputLabel.css";
export interface InputLabelProps {
id?: string;
htmlFor?: string;
/**
* The content of the component.
*/
@@ -1,6 +1,7 @@
import { mount, shallow } from "enzyme";
import { mount } from "enzyme";
import React from "react";
import { MediaQueryMatchers } from "react-responsive";
import { createRenderer } from "react-test-renderer/shallow";
import { PropTypesOf } from "talk-ui/types";
@@ -15,8 +16,9 @@ it("renders correctly", () => {
screen: true,
children: <div>Hello World</div>,
};
const wrapper = shallow(<MatchMedia {...props} />);
expect(wrapper).toMatchSnapshot();
const renderer = createRenderer();
renderer.render(<MatchMedia {...props} />);
expect(renderer.getRenderOutput()).toMatchSnapshot();
});
it("renders less than and great than correctly", () => {
@@ -25,8 +27,9 @@ it("renders less than and great than correctly", () => {
gtWidth: "sm",
children: <div>Hello World</div>,
};
const wrapper = shallow(<MatchMedia {...props} />);
expect(wrapper).toMatchSnapshot();
const renderer = createRenderer();
renderer.render(<MatchMedia {...props} />);
expect(renderer.getRenderOutput()).toMatchSnapshot();
});
it("map new speech prop to older aural prop", () => {
@@ -34,8 +37,9 @@ it("map new speech prop to older aural prop", () => {
speech: true,
children: <div>Hello World</div>,
};
const wrapper = shallow(<MatchMedia {...props} />);
expect(wrapper).toMatchSnapshot();
const renderer = createRenderer();
renderer.render(<MatchMedia {...props} />);
expect(renderer.getRenderOutput()).toMatchSnapshot();
});
it("should get mediaQueryValues from context", () => {
@@ -0,0 +1,60 @@
.root {
width: calc(29 * var(--spacing-unit));
height: 36px;
width: 100%;
line-height: 36px;
box-sizing: border-box;
}
.colorRegular {
background-color: var(--palette-common-white);
color: var(--palette-common-black);
border: 1px solid var(--palette-grey-light);
}
.colorError {
background-color: var(--palette-common-white);
border-color: var(--palette-error-main);
border: 2px solid var(--palette-error-darkest);
}
.fullWidth {
width: 100%;
}
.wrapper {
position: relative;
display: inline-flex;
align-items: center;
width: 100%;
height: 100%;
box-sizing: border-box;
}
.icon {
position: absolute;
display: inline-block;
right: 0px;
padding: 4px calc(1 * var(--spacing-unit));
cursor: pointer;
line-height: 0;
}
.input {
composes: inputText placeholderPseudo from "talk-ui/shared/typography.css";
display: block;
padding: calc(0.5 * var(--spacing-unit)) calc(3 * var(--spacing-unit))
calc(0.5 * var(--spacing-unit)) calc(0.5 * var(--spacing-unit));
border-radius: var(--round-corners);
width: 100%;
height: 100%;
box-sizing: border-box;
&:read-only {
background-color: var(--palette-grey-lightest);
}
&:disabled {
color: var(--palette-text-secondary);
background-color: var(--palette-grey-lightest);
}
}
@@ -0,0 +1,25 @@
---
name: PasswordField
menu: UI Kit
---
import { Playground, PropsTable } from "docz";
import PasswordField from "./PasswordField.tsx";
import HorizontalGutter from "../HorizontalGutter";
# PasswordField
## Basic Use
<Playground>
<HorizontalGutter>
<PasswordField placeholder="This is a placeholder" />
<PasswordField defaultValue="This is an input field" />
<PasswordField color="error" defaultValue="A PasswordField with an error" />
<PasswordField
color="error"
defaultValue="A PasswordField with an error"
fullWidth
/>
</HorizontalGutter>
</Playground>
@@ -0,0 +1,18 @@
import React from "react";
import TestRenderer from "react-test-renderer";
import { PropTypesOf } from "talk-ui/types";
import PasswordField from "./PasswordField";
it("renders correctly", () => {
const props: PropTypesOf<typeof PasswordField> = {
className: "custom",
defaultValue: "Hello World",
};
const renderer = TestRenderer.create(<PasswordField {...props} />);
expect(renderer.toJSON()).toMatchSnapshot();
renderer.root.findByProps({ role: "button" }).props.onClick();
expect(renderer.toJSON()).toMatchSnapshot();
});
@@ -0,0 +1,148 @@
import cn from "classnames";
import React, {
AllHTMLAttributes,
ChangeEvent,
Component,
EventHandler,
MouseEvent,
} from "react";
import { withStyles } from "talk-ui/hocs";
import Icon from "../Icon";
import styles from "./PasswordField.css";
export interface PasswordFieldProps {
id?: string;
/**
* The content value of the component.
*/
defaultValue?: string;
/**
* The content value of the component.
*/
value?: string;
/**
* Convenient prop to override the root styling.
*/
className?: string;
/**
* Override or extend the styles applied to the component.
*/
classes: typeof styles;
/**
* Color of the PasswordField
*/
color?: "regular" | "error";
/*
* If set renders a full width button
*/
fullWidth?: boolean;
/**
* Placeholder
*/
placeholder?: string;
/**
* Mark as readonly
*/
readOnly?: boolean;
/**
* Name
*/
name?: string;
/**
* onChange
*/
onChange?: EventHandler<ChangeEvent<HTMLInputElement>>;
disabled?: boolean;
autoComplete?: AllHTMLAttributes<HTMLInputElement>["autoComplete"];
autoCorrect?: AllHTMLAttributes<HTMLInputElement>["autoCorrect"];
autoCapitalize?: AllHTMLAttributes<HTMLInputElement>["autoCapitalize"];
spellCheck?: AllHTMLAttributes<HTMLInputElement>["spellCheck"];
showPasswordTitle?: string;
hidePasswordTitle?: string;
}
interface State {
reveal: boolean;
}
class PasswordField extends Component<PasswordFieldProps, State> {
public static defaultProps: Partial<PasswordFieldProps> = {
color: "regular",
placeholder: "",
showPasswordTitle: "Hide password",
hidePasswordTitle: "Show password",
};
public state = {
reveal: false,
};
private handleToggleVisibility = (e: MouseEvent) => {
this.setState(state => ({
reveal: !state.reveal,
}));
};
public render() {
const {
className,
classes,
color,
fullWidth,
value,
placeholder,
hidePasswordTitle,
showPasswordTitle,
...rest
} = this.props;
const rootClassName = cn(
{
[classes.fullWidth]: fullWidth,
},
classes.root,
className
);
const inputClassName = cn(
{
[classes.colorRegular]: color === "regular",
[classes.colorError]: color === "error",
[classes.fullWidth]: fullWidth,
},
classes.input
);
const reveal = this.state.reveal;
return (
<div className={rootClassName}>
<div className={classes.wrapper}>
<input
className={inputClassName}
placeholder={placeholder}
value={value}
type={reveal ? "text" : "password"}
{...rest}
/>
<div
role="button"
className={styles.icon}
title={reveal ? hidePasswordTitle : showPasswordTitle}
onClick={this.handleToggleVisibility}
tabIndex={0}
>
<Icon>{reveal ? "visibility_off" : "visibility"}</Icon>
</div>
</div>
</div>
);
}
}
const enhanced = withStyles(styles)(PasswordField);
export default enhanced;
@@ -0,0 +1,63 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
<div
className="PasswordField-root custom"
>
<div
className="PasswordField-wrapper"
>
<input
className="PasswordField-colorRegular PasswordField-input"
defaultValue="Hello World"
placeholder=""
type="password"
/>
<div
className="PasswordField-icon"
onClick={[Function]}
role="button"
tabIndex={0}
title="Hide password"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
visibility
</span>
</div>
</div>
</div>
`;
exports[`renders correctly 2`] = `
<div
className="PasswordField-root custom"
>
<div
className="PasswordField-wrapper"
>
<input
className="PasswordField-colorRegular PasswordField-input"
defaultValue="Hello World"
placeholder=""
type="text"
/>
<div
className="PasswordField-icon"
onClick={[Function]}
role="button"
tabIndex={0}
title="Show password"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
visibility_off
</span>
</div>
</div>
</div>
`;
@@ -0,0 +1,2 @@
export * from "./PasswordField";
export { default } from "./PasswordField";
@@ -1,5 +1,5 @@
import { shallow } from "enzyme";
import React from "react";
import { createRenderer } from "react-test-renderer/shallow";
import { PropTypesOf } from "talk-framework/types";
@@ -9,6 +9,7 @@ it("renders correctly", () => {
const props: PropTypesOf<typeof Navigation> = {
children: "children",
};
const wrapper = shallow(<Navigation {...props} />);
expect(wrapper).toMatchSnapshot();
const renderer = createRenderer();
renderer.render(<Navigation {...props} />);
expect(renderer.getRenderOutput()).toMatchSnapshot();
});
@@ -1,6 +1,6 @@
import { shallow } from "enzyme";
import { noop } from "lodash";
import React from "react";
import { createRenderer } from "react-test-renderer/shallow";
import { PropTypesOf } from "talk-framework/types";
@@ -14,6 +14,7 @@ it("renders correctly", () => {
active: true,
className: "custom",
};
const wrapper = shallow(<NavigationItem {...props} />);
expect(wrapper).toMatchSnapshot();
const renderer = createRenderer();
renderer.render(<NavigationItem {...props} />);
expect(renderer.getRenderOutput()).toMatchSnapshot();
});
@@ -1,5 +1,5 @@
import { shallow } from "enzyme";
import React from "react";
import { createRenderer } from "react-test-renderer/shallow";
import { PropTypesOf } from "talk-framework/types";
@@ -12,6 +12,7 @@ it("renders correctly", () => {
gutterEnd: true,
className: "custom",
};
const wrapper = shallow(<SubBar {...props} />);
expect(wrapper).toMatchSnapshot();
const renderer = createRenderer();
renderer.render(<SubBar {...props} />);
expect(renderer.getRenderOutput()).toMatchSnapshot();
});
+1
View File
@@ -45,3 +45,4 @@ export { BrandIcon, BrandName, Logo } from "./Brand";
export { default as Counter } from "./Counter";
export { Marker, Count as MarkerCount } from "./Marker";
export { default as Card } from "./Card";
export { default as PasswordField } from "./PasswordField";
+5 -1
View File
@@ -61,8 +61,12 @@ const variables = {
},
/* Divider */
divider: "rgba(0, 0, 0, 0.12)",
brand: "#F77160",
highlight: "#FFD863",
brand: {
main: "#f77160",
light: "#f97f70",
lighter: "#fc9e92",
},
},
/* gitter and spacing */
spacingUnitSmall: 5,