[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
@@ -0,0 +1,122 @@
import * as React from "react";
import { Component } from "react";
import { AccountCompletionContainer_auth as AuthData } from "talk-auth/__generated__/AccountCompletionContainer_auth.graphql";
import { AccountCompletionContainer_me as UserData } from "talk-auth/__generated__/AccountCompletionContainer_me.graphql";
import { AccountCompletionContainerLocal as Local } from "talk-auth/__generated__/AccountCompletionContainerLocal.graphql";
import {
CompleteAccountMutation,
SetViewMutation,
withCompleteAccountMutation,
withSetViewMutation,
} from "talk-auth/mutations";
import {
graphql,
withFragmentContainer,
withLocalStateContainer,
} from "talk-framework/lib/relay";
interface Props {
completeAccount: CompleteAccountMutation;
setView: SetViewMutation;
local: Local;
auth: AuthData;
me: UserData | null;
}
function handleAccountCompletion(props: Props) {
const {
local: { view, authToken },
me,
auth,
setView,
completeAccount,
} = props;
if (me) {
if (!me.email) {
if (view !== "ADD_EMAIL_ADDRESS") {
setView({ view: "ADD_EMAIL_ADDRESS" });
}
} else if (!me.username) {
if (view !== "CREATE_USERNAME") {
setView({ view: "CREATE_USERNAME" });
}
} else if (
!me.profiles.some(p => p.__typename === "LocalProfile") &&
auth.integrations.local.enabled &&
auth.integrations.local.targetFilter.stream
) {
if (view !== "CREATE_PASSWORD") {
setView({ view: "CREATE_PASSWORD" });
}
} else {
completeAccount({ authToken: authToken! });
return true;
}
}
return false;
}
interface State {
checkedCompletionStatus: boolean;
}
class AccountCompletionContainer extends Component<Props, State> {
public state = {
checkedCompletionStatus: false,
};
public componentDidMount() {
handleAccountCompletion(this.props);
this.setState({ checkedCompletionStatus: true });
}
public componentDidUpdate() {
handleAccountCompletion(this.props);
}
public render() {
const { children } = this.props;
// We skip first frame to check for completion status.
if (!this.state.checkedCompletionStatus) {
return null;
}
return <>{children}</>;
}
}
const enhanced = withLocalStateContainer(
graphql`
fragment AccountCompletionContainerLocal on Local {
authToken
view
}
`
)(
withFragmentContainer<Props>({
auth: graphql`
fragment AccountCompletionContainer_auth on Auth {
integrations {
local {
enabled
targetFilter {
stream
}
}
}
}
`,
me: graphql`
fragment AccountCompletionContainer_me on User {
username
email
profiles {
__typename
}
}
`,
})(
withSetViewMutation(withCompleteAccountMutation(AccountCompletionContainer))
)
);
export default enhanced;
@@ -1,18 +1,38 @@
import * as React from "react";
import { StatelessComponent } from "react";
import { Component } from "react";
import { AppContainer_auth as AuthData } from "talk-auth/__generated__/AppContainer_auth.graphql";
import { AppContainer_me as UserData } from "talk-auth/__generated__/AppContainer_me.graphql";
import { AppContainerLocal as Local } from "talk-auth/__generated__/AppContainerLocal.graphql";
import { graphql, withLocalStateContainer } from "talk-framework/lib/relay";
import {
graphql,
withFragmentContainer,
withLocalStateContainer,
} from "talk-framework/lib/relay";
import App from "../components/App";
import AccountCompletionContainer from "./AccountCompletionContainer";
interface InnerProps {
interface Props {
local: Local;
auth: AuthData;
me: UserData | null;
}
const AppContainer: StatelessComponent<InnerProps> = ({ local: { view } }) => {
return <App view={view} />;
};
class AppContainer extends Component<Props> {
public render() {
const {
local: { view },
auth,
me,
} = this.props;
return (
<AccountCompletionContainer auth={auth} me={me}>
<App view={view} auth={auth} />
</AccountCompletionContainer>
);
}
}
const enhanced = withLocalStateContainer(
graphql`
@@ -20,6 +40,21 @@ const enhanced = withLocalStateContainer(
view
}
`
)(AppContainer);
)(
withFragmentContainer<Props>({
auth: graphql`
fragment AppContainer_auth on Auth {
...SignInContainer_auth
...SignUpContainer_auth
...AccountCompletionContainer_auth
}
`,
me: graphql`
fragment AppContainer_me on User {
...AccountCompletionContainer_me
}
`,
})(AppContainer)
);
export default enhanced;
@@ -1,11 +0,0 @@
import React, { Component } from "react";
import ForgotPassword from "../components/ForgotPassword";
class ForgotPasswordContainer extends Component {
public render() {
// tslint:disable-next-line:no-empty
return <ForgotPassword onSubmit={() => {}} />;
}
}
export default ForgotPasswordContainer;
@@ -1,11 +0,0 @@
import React, { Component } from "react";
import ResetPassword from "../components/ResetPassword";
class ResetPasswordContainer extends Component {
public render() {
// tslint:disable-next-line:no-empty
return <ResetPassword onSubmit={() => {}} />;
}
}
export default ResetPasswordContainer;
@@ -1,40 +0,0 @@
import { FORM_ERROR } from "final-form";
import React, { Component } from "react";
import SignIn, { SignInForm } from "../components/SignIn";
import {
SetViewMutation,
SignInMutation,
withSetViewMutation,
withSignInMutation,
} from "../mutations";
interface SignInContainerProps {
signIn: SignInMutation;
setView: SetViewMutation;
}
class SignInContainer extends Component<SignInContainerProps> {
private onSubmit: SignInForm["onSubmit"] = async (input, form) => {
try {
await this.props.signIn(input);
return form.reset();
} catch (error) {
return { [FORM_ERROR]: error.message };
}
};
private goToForgotPassword = () =>
this.props.setView({ view: "FORGOT_PASSWORD" });
private goToSignUp = () => this.props.setView({ view: "SIGN_UP" });
public render() {
return (
<SignIn
onSubmit={this.onSubmit}
onGotoForgotPassword={this.goToForgotPassword}
onGotoSignUp={this.goToSignUp}
/>
);
}
}
const enhanced = withSetViewMutation(withSignInMutation(SignInContainer));
export default enhanced;
@@ -1,33 +0,0 @@
import { FORM_ERROR } from "final-form";
import React, { Component } from "react";
import SignUp, { SignUpForm } from "../components/SignUp";
import {
SetViewMutation,
SignUpMutation,
withSetViewMutation,
withSignUpMutation,
} from "../mutations";
interface SignUpContainerProps {
signUp: SignUpMutation;
setView: SetViewMutation;
}
class SignUpContainer extends Component<SignUpContainerProps> {
private onSubmit: SignUpForm["onSubmit"] = async (input, form) => {
try {
await this.props.signUp(input);
return form.reset();
} catch (error) {
return { [FORM_ERROR]: error.message };
}
};
private goToSignIn = () => this.props.setView({ view: "SIGN_IN" });
public render() {
return <SignUp onSubmit={this.onSubmit} onGotoSignIn={this.goToSignIn} />;
}
}
const enhanced = withSetViewMutation(withSignUpMutation(SignUpContainer));
export default enhanced;