mirror of
https://github.com/wassname/talk.git
synced 2026-08-14 12:50:17 +08:00
[next] Email (#2261)
* feat: suspending, banning, now propogation * feat: added email rendering + localization support * fix: fix related to lib * refactor: moved juicer to queue task * refactor: cleanup of job processor * refactor: improved error messaging around failed email * feat: initial forgot passwor impl * fix: fixed rebase errors * feat: send back Content-Language header with requests * feat: added ban email * feat: implemented forgotten password API * fix: linting * feat: support more emails * fix: promise patches * feat: initial confirm email API * feat: added rate limiting * feat: added URL support * feat: added email docs * fix: updated docs * chore: documentation review * fix: fixed build bug * feat: implement forgot password in auth popup * test: add tests + fixes * chore: rename StatelessComponent to FunctionComponent * fix: types and test fixes * chore: upgrade deps * fix: THANK YOU TESTS FOR SAVING MY A** * chore: reorder imports * chore: remove obsolete ! * feat: implement accounts bundle * refactor: review suggestion * fix: rebase upgrade error * fix: rebase bug * feat: reset password link support * test: add tests for account password reset page * fix: remove redirect uri * fix: revert local state changes
This commit is contained in:
@@ -1,12 +1,11 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
|
||||
import AddEmailAddressContainer from "../views/addEmailAddress/containers/AddEmailAddressContainer";
|
||||
import CreatePasswordContainer from "../views/createPassword/containers/CreatePasswordContainer";
|
||||
import CreateUsernameContainer from "../views/createUsername/containers/CreateUsernameContainer";
|
||||
import ForgotPasswordContainer from "../views/forgotPassword/containers/ForgotPasswordContainer";
|
||||
import ResetPasswordContainer from "../views/resetPassword/containers/ResetPasswordContainer";
|
||||
import ForgotPassword from "../views/forgotPassword/components/ForgotPassword";
|
||||
import SignInContainer from "../views/signIn/containers/SignInContainer";
|
||||
import SignUpContainer from "../views/signUp/containers/SignUpContainer";
|
||||
import "./App.css";
|
||||
@@ -15,7 +14,6 @@ export type View =
|
||||
| "SIGN_UP"
|
||||
| "SIGN_IN"
|
||||
| "FORGOT_PASSWORD"
|
||||
| "RESET_PASSWORD"
|
||||
| "CREATE_USERNAME"
|
||||
| "CREATE_PASSWORD"
|
||||
| "ADD_EMAIL_ADDRESS"
|
||||
@@ -34,9 +32,7 @@ const renderView = (view: AppProps["view"], auth: AppProps["auth"]) => {
|
||||
case "SIGN_IN":
|
||||
return <SignInContainer auth={auth} />;
|
||||
case "FORGOT_PASSWORD":
|
||||
return <ForgotPasswordContainer />;
|
||||
case "RESET_PASSWORD":
|
||||
return <ResetPasswordContainer />;
|
||||
return <ForgotPassword />;
|
||||
case "CREATE_USERNAME":
|
||||
return <CreateUsernameContainer />;
|
||||
case "CREATE_PASSWORD":
|
||||
@@ -48,7 +44,7 @@ const renderView = (view: AppProps["view"], auth: AppProps["auth"]) => {
|
||||
}
|
||||
};
|
||||
|
||||
const App: StatelessComponent<AppProps> = ({ view, auth }) => (
|
||||
const App: FunctionComponent<AppProps> = ({ view, auth }) => (
|
||||
<div>{renderView(view, auth)}</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import * as React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
import { FunctionComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
import {
|
||||
composeValidators,
|
||||
@@ -18,7 +18,7 @@ interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const ConfirmEmailField: StatelessComponent<Props> = props => (
|
||||
const ConfirmEmailField: FunctionComponent<Props> = props => (
|
||||
<Field
|
||||
name="confirmEmail"
|
||||
validate={composeValidators(required, validateEqualEmails)}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import * as React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
import { FunctionComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
|
||||
import {
|
||||
@@ -19,7 +19,7 @@ interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const SetPasswordField: StatelessComponent<Props> = props => (
|
||||
const SetPasswordField: FunctionComponent<Props> = props => (
|
||||
<Field
|
||||
name="confirmPassword"
|
||||
validate={composeValidators(required, validateEqualPasswords)}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import * as React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
import { FunctionComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
import {
|
||||
composeValidators,
|
||||
@@ -18,7 +18,7 @@ interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const EmailField: StatelessComponent<Props> = props => (
|
||||
const EmailField: FunctionComponent<Props> = props => (
|
||||
<Field name="email" validate={composeValidators(required, validateEmail)}>
|
||||
{({ input, meta }) => (
|
||||
<FormField>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
import { FunctionComponent } from "react";
|
||||
|
||||
import { Flex } from "talk-ui/components";
|
||||
|
||||
@@ -9,7 +9,7 @@ export interface BarProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const Bar: StatelessComponent<BarProps> = props => (
|
||||
const Bar: FunctionComponent<BarProps> = props => (
|
||||
<Flex className={styles.root} alignItems="center" justifyContent="center">
|
||||
<div>{props.children}</div>
|
||||
</Flex>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
import { FunctionComponent } from "react";
|
||||
|
||||
import { Flex } from "talk-ui/components";
|
||||
|
||||
@@ -9,7 +9,7 @@ export interface SubBarProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const SubBar: StatelessComponent<SubBarProps> = props => (
|
||||
const SubBar: FunctionComponent<SubBarProps> = props => (
|
||||
<Flex className={styles.root} alignItems="center" justifyContent="center">
|
||||
<div>{props.children}</div>
|
||||
</Flex>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
import { FunctionComponent } from "react";
|
||||
|
||||
import { Typography } from "talk-ui/components";
|
||||
import styles from "./Subtitle.css";
|
||||
@@ -8,7 +8,7 @@ interface Props {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const Subtitle: StatelessComponent<Props> = props => (
|
||||
const Subtitle: FunctionComponent<Props> = props => (
|
||||
<Typography variant="heading4" align="center" className={styles.root}>
|
||||
{props.children}
|
||||
</Typography>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
import { FunctionComponent } from "react";
|
||||
|
||||
import { Typography } from "talk-ui/components";
|
||||
import styles from "./Title.css";
|
||||
@@ -8,7 +8,7 @@ interface Props {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const Title: StatelessComponent<Props> = props => (
|
||||
const Title: FunctionComponent<Props> = props => (
|
||||
<Typography variant="heading2" align="center" className={styles.root}>
|
||||
{props.children}
|
||||
</Typography>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
import { FunctionComponent } from "react";
|
||||
|
||||
import { Flex } from "talk-ui/components";
|
||||
|
||||
@@ -9,7 +9,7 @@ interface Props {
|
||||
children: string;
|
||||
}
|
||||
|
||||
const HorizontalSeparator: StatelessComponent<Props> = props => (
|
||||
const HorizontalSeparator: FunctionComponent<Props> = props => (
|
||||
<Flex className={styles.root} alignItems="center" justifyContent="center">
|
||||
<hr className={styles.hr} />
|
||||
<div className={styles.text}>{props.children}</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import cn from "classnames";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import styles from "./Main.css";
|
||||
|
||||
@@ -8,7 +8,7 @@ export interface MainProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const Main: StatelessComponent<MainProps> = ({
|
||||
const Main: FunctionComponent<MainProps> = ({
|
||||
children,
|
||||
className,
|
||||
...rest
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
import { FunctionComponent } from "react";
|
||||
|
||||
import HorizontalSeparator from "./HorizontalSeparator";
|
||||
|
||||
const OrSeparator: StatelessComponent = () => (
|
||||
const OrSeparator: FunctionComponent = () => (
|
||||
<Localized id="general-orSeparator">
|
||||
<HorizontalSeparator>Or</HorizontalSeparator>
|
||||
</Localized>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import * as React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
import { FunctionComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
import { PasswordField } from "talk-framework/components";
|
||||
import {
|
||||
@@ -19,7 +19,7 @@ interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const SetPasswordField: StatelessComponent<Props> = props => (
|
||||
const SetPasswordField: FunctionComponent<Props> = props => (
|
||||
<Field
|
||||
name="password"
|
||||
validate={composeValidators(required, validatePassword)}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import * as React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
import { FunctionComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
import {
|
||||
composeValidators,
|
||||
@@ -19,7 +19,7 @@ interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const CreateUsernameField: StatelessComponent<Props> = props => (
|
||||
const CreateUsernameField: FunctionComponent<Props> = props => (
|
||||
<Field
|
||||
name="username"
|
||||
validate={composeValidators(required, validateUsername)}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
exports[`renders sign in 1`] = `
|
||||
<div>
|
||||
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(withContext(withLocalStateContainer(Relay(SignInContainer)))))))))
|
||||
<withContext(withMutation(withContext(createMutationContainer(withContext(createMutationContainer(withContext(withLocalStateContainer(Relay(SignInContainer)))))))))
|
||||
auth={Object {}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -8,17 +8,18 @@ import {
|
||||
CompleteAccountMutation,
|
||||
SetViewMutation,
|
||||
withCompleteAccountMutation,
|
||||
withSetViewMutation,
|
||||
} from "talk-auth/mutations";
|
||||
import {
|
||||
graphql,
|
||||
MutationProp,
|
||||
withFragmentContainer,
|
||||
withLocalStateContainer,
|
||||
withMutation,
|
||||
} from "talk-framework/lib/relay";
|
||||
|
||||
interface Props {
|
||||
completeAccount: CompleteAccountMutation;
|
||||
setView: SetViewMutation;
|
||||
setView: MutationProp<typeof SetViewMutation>;
|
||||
local: Local;
|
||||
auth: AuthData;
|
||||
viewer: UserData | null;
|
||||
@@ -119,7 +120,9 @@ const enhanced = withLocalStateContainer(
|
||||
}
|
||||
`,
|
||||
})(
|
||||
withSetViewMutation(withCompleteAccountMutation(AccountCompletionContainer))
|
||||
withMutation(SetViewMutation)(
|
||||
withCompleteAccountMutation(AccountCompletionContainer)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { FunctionComponent, useEffect } from "react";
|
||||
|
||||
import { SetViewMutation } from "talk-auth/mutations";
|
||||
import { View } from "talk-auth/mutations/SetViewMutation";
|
||||
import { parseQuery } from "talk-common/utils";
|
||||
import { useMutation } from "talk-framework/lib/relay";
|
||||
|
||||
/**
|
||||
* ViewRouterContainer listens for changes to the location
|
||||
* parses the `?view=` and calls the `setView` mutation.
|
||||
*/
|
||||
const ViewRouterContainer: FunctionComponent = () => {
|
||||
const setView = useMutation(SetViewMutation);
|
||||
useEffect(() => {
|
||||
window.onpopstate = () => {
|
||||
const query = parseQuery(window.location.search);
|
||||
setView({ view: query.view as View });
|
||||
};
|
||||
return () => {
|
||||
window.onpopstate = null;
|
||||
};
|
||||
}, [setView]);
|
||||
return null;
|
||||
};
|
||||
|
||||
export default ViewRouterContainer;
|
||||
@@ -0,0 +1,6 @@
|
||||
import { View } from "talk-auth/mutations/SetViewMutation";
|
||||
import { modifyQuery } from "talk-framework/utils";
|
||||
|
||||
export default function getViewURL(view: View) {
|
||||
return modifyQuery(window.location.href, { view });
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as getViewURL } from "./getViewURL";
|
||||
@@ -1,9 +1,10 @@
|
||||
import React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
import { FunctionComponent } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
import { createManaged } from "talk-framework/lib/bootstrap";
|
||||
|
||||
import ViewRouterContainer from "./containers/ViewRouterContainer";
|
||||
import resizePopup from "./dom/resizePopup";
|
||||
import { initLocalState } from "./local";
|
||||
import localesData from "./locales";
|
||||
@@ -35,9 +36,12 @@ async function main() {
|
||||
userLocales: navigator.languages,
|
||||
});
|
||||
|
||||
const Index: StatelessComponent = () => (
|
||||
const Index: FunctionComponent = () => (
|
||||
<ManagedTalkContextProvider>
|
||||
<AppQuery />
|
||||
<>
|
||||
<ViewRouterContainer />
|
||||
<AppQuery />
|
||||
</>
|
||||
</ManagedTalkContextProvider>
|
||||
);
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ enum View {
|
||||
SIGN_UP
|
||||
SIGN_IN
|
||||
FORGOT_PASSWORD
|
||||
RESET_PASSWORD
|
||||
CREATE_USERNAME
|
||||
CREATE_PASSWORD
|
||||
ADD_EMAIL_ADDRESS
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { pick } from "lodash";
|
||||
|
||||
import { createMutation } from "talk-framework/lib/relay";
|
||||
import { forgotPassword, ForgotPasswordInput } from "talk-framework/rest";
|
||||
|
||||
const ForgotPasswordMutation = createMutation(
|
||||
"forgotPassword",
|
||||
(_, input: ForgotPasswordInput, { rest }) =>
|
||||
forgotPassword(rest, pick(input, ["email"]))
|
||||
);
|
||||
|
||||
export default ForgotPasswordMutation;
|
||||
@@ -3,7 +3,7 @@ import { Environment, RecordSource } from "relay-runtime";
|
||||
import { LOCAL_ID } from "talk-framework/lib/relay";
|
||||
import { createRelayEnvironment } from "talk-framework/testHelpers";
|
||||
|
||||
import { commit } from "./SetViewMutation";
|
||||
import SetViewMutation from "./SetViewMutation";
|
||||
|
||||
let environment: Environment;
|
||||
const source: RecordSource = new RecordSource();
|
||||
@@ -16,6 +16,6 @@ beforeAll(() => {
|
||||
|
||||
it("Sets view", () => {
|
||||
const view = "SIGN_UP";
|
||||
commit(environment, { view }, {} as any);
|
||||
SetViewMutation.commit(environment, { view }, {} as any);
|
||||
expect(source.get(LOCAL_ID)!.view).toEqual(view);
|
||||
});
|
||||
|
||||
@@ -1,32 +1,56 @@
|
||||
import { commitLocalUpdate, Environment } from "relay-runtime";
|
||||
|
||||
import { TalkContext } from "talk-framework/lib/bootstrap";
|
||||
import { createMutationContainer } from "talk-framework/lib/relay";
|
||||
import { createMutation } from "talk-framework/lib/relay";
|
||||
import { LOCAL_ID } from "talk-framework/lib/relay/withLocalStateContainer";
|
||||
|
||||
export type View =
|
||||
| "SIGN_IN"
|
||||
| "SIGN_UP"
|
||||
| "FORGOT_PASSWORD"
|
||||
| "ADD_EMAIL_ADDRESS"
|
||||
| "CREATE_USERNAME"
|
||||
| "CREATE_PASSWORD";
|
||||
|
||||
export interface SetViewInput {
|
||||
// TODO: replace with generated typescript types.
|
||||
view:
|
||||
| "SIGN_IN"
|
||||
| "SIGN_UP"
|
||||
| "FORGOT_PASSWORD"
|
||||
| "RESET_PASSWORD"
|
||||
| "ADD_EMAIL_ADDRESS"
|
||||
| "CREATE_USERNAME"
|
||||
| "CREATE_PASSWORD";
|
||||
view: View;
|
||||
history?: "push" | "replace";
|
||||
}
|
||||
|
||||
export type SetViewMutation = (input: SetViewInput) => Promise<void>;
|
||||
const SetViewMutation = createMutation(
|
||||
"setView",
|
||||
(environment: Environment, input: SetViewInput) => {
|
||||
return commitLocalUpdate(environment, store => {
|
||||
const record = store.get(LOCAL_ID)!;
|
||||
|
||||
export async function commit(
|
||||
environment: Environment,
|
||||
input: SetViewInput,
|
||||
{ pym }: TalkContext
|
||||
) {
|
||||
return commitLocalUpdate(environment, store => {
|
||||
const record = store.get(LOCAL_ID)!;
|
||||
record.setValue(input.view, "view");
|
||||
});
|
||||
}
|
||||
if (input.history) {
|
||||
const newLocation = window.location.href.replace(
|
||||
/\?[^#]*/,
|
||||
`?view=${input.view}`
|
||||
);
|
||||
const previousState = window.history.state;
|
||||
switch (input.history) {
|
||||
case "push":
|
||||
window.history.pushState(
|
||||
previousState,
|
||||
document.title,
|
||||
newLocation
|
||||
);
|
||||
break;
|
||||
case "replace":
|
||||
window.history.replaceState(
|
||||
previousState,
|
||||
document.title,
|
||||
newLocation
|
||||
);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
export const withSetViewMutation = createMutationContainer("setView", commit);
|
||||
record.setValue(input.view, "view");
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
export default SetViewMutation;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export { withSetViewMutation, SetViewMutation } from "./SetViewMutation";
|
||||
export { default as SetViewMutation } from "./SetViewMutation";
|
||||
export { withSignInMutation, SignInMutation } from "./SignInMutation";
|
||||
export {
|
||||
withCompleteAccountMutation,
|
||||
@@ -18,3 +18,4 @@ export {
|
||||
withSetPasswordMutation,
|
||||
SetPasswordMutation,
|
||||
} from "./SetPasswordMutation";
|
||||
export { default as ForgotPasswordMutation } from "./ForgotPasswordMutation";
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders forgot password view 1`] = `
|
||||
<div>
|
||||
<div
|
||||
data-testid="forgotPassword-container"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Bar-root Flex-flex Flex-justifyCenter Flex-alignCenter"
|
||||
>
|
||||
<div>
|
||||
<h1
|
||||
className="Typography-root Typography-heading2 Typography-colorTextPrimary Typography-alignCenter Title-root"
|
||||
>
|
||||
Forgot Password?
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root SubBar-root Flex-flex Flex-justifyCenter Flex-alignCenter"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="Flex-root Typography-root Typography-bodyCopy Typography-colorTextPrimary Flex-flex"
|
||||
>
|
||||
<a
|
||||
className="TextLink-root"
|
||||
href="http://localhost/?view=SIGN_IN"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Go back to sign in page
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="Main-root"
|
||||
data-testid="forgotPassword-main"
|
||||
>
|
||||
<form
|
||||
autoComplete="off"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<p
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
Enter your email address below and we will send you a link to
|
||||
reset your password.
|
||||
</p>
|
||||
<div
|
||||
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<label
|
||||
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
htmlFor="email"
|
||||
>
|
||||
Email Address
|
||||
</label>
|
||||
<div
|
||||
className="TextField-root TextField-fullWidth"
|
||||
>
|
||||
<input
|
||||
className="TextField-input TextField-colorRegular"
|
||||
disabled={false}
|
||||
id="email"
|
||||
name="email"
|
||||
onChange={[Function]}
|
||||
placeholder="Email Address"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Send Email
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -89,20 +89,17 @@ exports[`accepts correct password 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-justifyFlexEnd"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
data-testid="gotoForgotPasswordButton"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
<p
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
Forgot your password?
|
||||
</button>
|
||||
<a
|
||||
className="TextLink-root"
|
||||
href="http://localhost/?view=FORGOT_PASSWORD"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Forgot your password?
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@@ -218,20 +215,17 @@ exports[`accepts valid email 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-justifyFlexEnd"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
data-testid="gotoForgotPasswordButton"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
<p
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
Forgot your password?
|
||||
</button>
|
||||
<a
|
||||
className="TextLink-root"
|
||||
href="http://localhost/?view=FORGOT_PASSWORD"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Forgot your password?
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@@ -341,20 +335,17 @@ exports[`auth configuration renders all auth enabled 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-justifyFlexEnd"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
data-testid="gotoForgotPasswordButton"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
<p
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
Forgot your password?
|
||||
</button>
|
||||
<a
|
||||
className="TextLink-root"
|
||||
href="http://localhost/?view=FORGOT_PASSWORD#accessToken=access-token"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Forgot your password?
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@@ -635,20 +626,17 @@ exports[`checks for invalid email 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-justifyFlexEnd"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
data-testid="gotoForgotPasswordButton"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
<p
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
Forgot your password?
|
||||
</button>
|
||||
<a
|
||||
className="TextLink-root"
|
||||
href="http://localhost/?view=FORGOT_PASSWORD"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Forgot your password?
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@@ -706,19 +694,13 @@ exports[`renders sign in view 1`] = `
|
||||
<span>
|
||||
Don't have an account?
|
||||
</span>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
data-testid="gotoSignUpButton"
|
||||
onBlur={[Function]}
|
||||
<a
|
||||
className="TextLink-root"
|
||||
href="http://localhost/?view=SIGN_UP"
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Sign Up
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -804,20 +786,17 @@ exports[`renders sign in view 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-justifyFlexEnd"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
data-testid="gotoForgotPasswordButton"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
<p
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
Forgot your password?
|
||||
</button>
|
||||
<a
|
||||
className="TextLink-root"
|
||||
href="http://localhost/?view=FORGOT_PASSWORD"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Forgot your password?
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@@ -881,19 +860,13 @@ exports[`renders sign in view with error 1`] = `
|
||||
<span>
|
||||
Don't have an account?
|
||||
</span>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
data-testid="gotoSignUpButton"
|
||||
onBlur={[Function]}
|
||||
<a
|
||||
className="TextLink-root"
|
||||
href="http://localhost/?view=SIGN_UP"
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Sign Up
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -984,20 +957,17 @@ exports[`renders sign in view with error 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-justifyFlexEnd"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
data-testid="gotoForgotPasswordButton"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
<p
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
Forgot your password?
|
||||
</button>
|
||||
<a
|
||||
className="TextLink-root"
|
||||
href="http://localhost/?view=FORGOT_PASSWORD"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Forgot your password?
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@@ -1132,20 +1102,17 @@ exports[`shows error when submitting empty form 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-justifyFlexEnd"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
data-testid="gotoForgotPasswordButton"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
<p
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
Forgot your password?
|
||||
</button>
|
||||
<a
|
||||
className="TextLink-root"
|
||||
href="http://localhost/?view=FORGOT_PASSWORD"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Forgot your password?
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@@ -1253,20 +1220,17 @@ exports[`shows server error 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-justifyFlexEnd"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
data-testid="gotoForgotPasswordButton"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
<p
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
Forgot your password?
|
||||
</button>
|
||||
<a
|
||||
className="TextLink-root"
|
||||
href="http://localhost/?view=FORGOT_PASSWORD"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Forgot your password?
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@@ -1369,20 +1333,17 @@ exports[`submits form successfully 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-justifyFlexEnd"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
data-testid="gotoForgotPasswordButton"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
<p
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
Forgot your password?
|
||||
</button>
|
||||
<a
|
||||
className="TextLink-root"
|
||||
href="http://localhost/?view=FORGOT_PASSWORD"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Forgot your password?
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
|
||||
@@ -1747,19 +1747,13 @@ exports[`renders sign up form 1`] = `
|
||||
<span>
|
||||
Already have an account?
|
||||
</span>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
data-testid="gotoSignInButton"
|
||||
onBlur={[Function]}
|
||||
<a
|
||||
className="TextLink-root"
|
||||
href="http://localhost/?view=SIGN_IN"
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
import { act } from "react-test-renderer";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { pureMerge } from "talk-common/utils";
|
||||
import { GQLResolver } from "talk-framework/schema";
|
||||
import {
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
wait,
|
||||
waitForElement,
|
||||
within,
|
||||
} from "talk-framework/testHelpers";
|
||||
|
||||
import create from "./create";
|
||||
import { settings } from "./fixtures";
|
||||
import mockWindow from "./mockWindow";
|
||||
|
||||
let windowMock: ReturnType<typeof mockWindow>;
|
||||
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
const { testRenderer, context } = create({
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue("FORGOT_PASSWORD", "view");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
const container = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("forgotPassword-container")
|
||||
);
|
||||
const main = within(testRenderer.root).getByTestID(/.*-main/);
|
||||
const form = within(main).queryByType("form")!;
|
||||
const emailField = within(form).getByLabelText("Email Address");
|
||||
|
||||
return {
|
||||
context,
|
||||
testRenderer,
|
||||
form,
|
||||
main,
|
||||
container,
|
||||
emailField,
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
windowMock = mockWindow();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await wait(() => expect(windowMock.resizeStub.called).toBe(true));
|
||||
windowMock.restore();
|
||||
});
|
||||
|
||||
it("renders forgot password view", async () => {
|
||||
const { testRenderer } = await createTestRenderer();
|
||||
expect(testRenderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("shows error when submitting empty form", async () => {
|
||||
const { form } = await createTestRenderer();
|
||||
act(() => {
|
||||
form!.props.onSubmit();
|
||||
});
|
||||
within(form).getByText("This field is required", { exact: false });
|
||||
});
|
||||
|
||||
it("checks for invalid email", async () => {
|
||||
const { form, emailField } = await createTestRenderer();
|
||||
act(() => {
|
||||
emailField.props.onChange("invalidemail");
|
||||
form!.props.onSubmit();
|
||||
});
|
||||
within(form).getByText("Please enter a valid email address", {
|
||||
exact: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("shows server error", async () => {
|
||||
const { form, context, emailField } = await createTestRenderer();
|
||||
const error = new Error("Server Error");
|
||||
const restMock = sinon.mock(context.rest);
|
||||
restMock
|
||||
.expects("fetch")
|
||||
.withArgs("/auth/local/forgot", {
|
||||
method: "POST",
|
||||
body: {
|
||||
email: "hans@test.com",
|
||||
},
|
||||
})
|
||||
.once()
|
||||
.throws(error);
|
||||
|
||||
act(() => {
|
||||
emailField.props.onChange("hans@test.com");
|
||||
form!.props.onSubmit();
|
||||
});
|
||||
|
||||
await waitForElement(() =>
|
||||
within(form).getByText("Server Error", {
|
||||
exact: false,
|
||||
})
|
||||
);
|
||||
|
||||
restMock.verify();
|
||||
});
|
||||
|
||||
it("submits form successfully", async () => {
|
||||
const {
|
||||
form,
|
||||
context,
|
||||
emailField,
|
||||
testRenderer,
|
||||
} = await createTestRenderer();
|
||||
const restMock = sinon.mock(context.rest);
|
||||
restMock
|
||||
.expects("fetch")
|
||||
.withArgs("/auth/local/forgot", {
|
||||
method: "POST",
|
||||
body: {
|
||||
email: "hans@test.com",
|
||||
},
|
||||
})
|
||||
.once();
|
||||
|
||||
act(() => {
|
||||
emailField.props.onChange("hans@test.com");
|
||||
form!.props.onSubmit();
|
||||
});
|
||||
|
||||
await waitForElement(() =>
|
||||
within(testRenderer.root).getByText("Check Your Email", { exact: false })
|
||||
);
|
||||
within(testRenderer.root)
|
||||
.getByText("Close")
|
||||
.props.onClick();
|
||||
|
||||
restMock.verify();
|
||||
|
||||
expect(windowMock.closeStub.called).toBe(true);
|
||||
});
|
||||
@@ -49,8 +49,8 @@ it("navigates to sign up form", async () => {
|
||||
within(testRenderer.root).getByTestID("signIn-container")
|
||||
);
|
||||
within(container)
|
||||
.getByTestID("gotoSignUpButton")
|
||||
.props.onClick();
|
||||
.getByText("Sign Up")
|
||||
.props.onClick({});
|
||||
await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("signUp-container")
|
||||
);
|
||||
@@ -62,8 +62,8 @@ it("navigates to sign in form", async () => {
|
||||
within(testRenderer.root).getByTestID("signUp-container")
|
||||
);
|
||||
within(container)
|
||||
.getByTestID("gotoSignInButton")
|
||||
.props.onClick();
|
||||
.getByText("Sign In")
|
||||
.props.onClick({});
|
||||
await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("signIn-container")
|
||||
);
|
||||
@@ -75,9 +75,22 @@ it("navigates to forgot password form", async () => {
|
||||
within(testRenderer.root).getByTestID("signIn-container")
|
||||
);
|
||||
within(container)
|
||||
.getByTestID("gotoForgotPasswordButton")
|
||||
.props.onClick();
|
||||
.getByText("Forgot your password?")
|
||||
.props.onClick({});
|
||||
await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("forgotPassword-container")
|
||||
);
|
||||
});
|
||||
|
||||
it("navigates back from forgot password form", async () => {
|
||||
const testRenderer = await createTestRenderer("FORGOT_PASSWORD");
|
||||
const container = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("forgotPassword-container")
|
||||
);
|
||||
within(container)
|
||||
.getByText("back to sign in", { exact: false })
|
||||
.props.onClick({});
|
||||
await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("signIn-container")
|
||||
);
|
||||
});
|
||||
|
||||
@@ -74,11 +74,11 @@ it("renders sign in view with error", async () => {
|
||||
);
|
||||
expect(within(container).toJSON()).toMatchSnapshot();
|
||||
within(testRenderer.root)
|
||||
.getByTestID("gotoSignUpButton")
|
||||
.props.onClick();
|
||||
.getByText("Sign Up")
|
||||
.props.onClick({});
|
||||
within(testRenderer.root)
|
||||
.getByTestID("gotoSignInButton")
|
||||
.props.onClick();
|
||||
.getByText("Sign In")
|
||||
.props.onClick({});
|
||||
const container2 = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("signIn-container")
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import React, { FunctionComponent } from "react";
|
||||
import { Form } from "react-final-form";
|
||||
|
||||
import { Bar, Title } from "talk-auth/components//Header";
|
||||
@@ -26,7 +26,7 @@ export interface AddEmailAddressForm {
|
||||
onSubmit: OnSubmit<FormProps>;
|
||||
}
|
||||
|
||||
const AddEmailAddress: StatelessComponent<AddEmailAddressForm> = props => {
|
||||
const AddEmailAddress: FunctionComponent<AddEmailAddressForm> = props => {
|
||||
return (
|
||||
<div data-testid="addEmailAddress-container">
|
||||
<Bar>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
import { FunctionComponent } from "react";
|
||||
|
||||
import { Icon } from "talk-ui/components";
|
||||
|
||||
@@ -9,7 +9,7 @@ interface Props {
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
const ListItem: StatelessComponent<Props> = props => (
|
||||
const ListItem: FunctionComponent<Props> = props => (
|
||||
<li className={styles.root}>
|
||||
<div className={styles.leftCol}>{props.icon}</div>
|
||||
<div>{props.children}</div>
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
import React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
import { FunctionComponent } from "react";
|
||||
|
||||
import styles from "./UnorderedList.css";
|
||||
|
||||
const UnorderedList: StatelessComponent = props => (
|
||||
const UnorderedList: FunctionComponent = props => (
|
||||
<ul className={styles.root}>{props.children}</ul>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import React, { FunctionComponent } from "react";
|
||||
import { Form } from "react-final-form";
|
||||
|
||||
import { Bar, Title } from "talk-auth/components//Header";
|
||||
@@ -23,7 +23,7 @@ export interface CreatePasswordForm {
|
||||
onSubmit: OnSubmit<FormProps>;
|
||||
}
|
||||
|
||||
const CreatePassword: StatelessComponent<CreatePasswordForm> = props => {
|
||||
const CreatePassword: FunctionComponent<CreatePasswordForm> = props => {
|
||||
return (
|
||||
<div data-testid="createPassword-container">
|
||||
<Bar>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import React, { FunctionComponent } from "react";
|
||||
import { Form } from "react-final-form";
|
||||
|
||||
import { Bar, Title } from "talk-auth/components//Header";
|
||||
@@ -23,7 +23,7 @@ export interface CreateUsernameForm {
|
||||
onSubmit: OnSubmit<FormProps>;
|
||||
}
|
||||
|
||||
const CreateUsername: StatelessComponent<CreateUsernameForm> = props => {
|
||||
const CreateUsername: FunctionComponent<CreateUsernameForm> = props => {
|
||||
return (
|
||||
<div data-testid="createUsername-container">
|
||||
<Bar>
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { FunctionComponent, useCallback } from "react";
|
||||
|
||||
import { Bar, Title } from "talk-auth/components/Header";
|
||||
import Main from "talk-auth/components/Main";
|
||||
import { Button, HorizontalGutter, Typography } from "talk-ui/components";
|
||||
|
||||
interface Props {
|
||||
email: string;
|
||||
}
|
||||
|
||||
const CheckEmail: FunctionComponent<Props> = ({ email }) => {
|
||||
const closeWindow = useCallback(() => {
|
||||
window.close();
|
||||
}, []);
|
||||
const UserEmail = () => <strong>{email}</strong>;
|
||||
return (
|
||||
<div data-testid="forgotPassword-checkEmail-container">
|
||||
<Bar>
|
||||
<Localized id="forgotPassword-checkEmail-checkEmailHeader">
|
||||
<Title>Check Your Email</Title>
|
||||
</Localized>
|
||||
</Bar>
|
||||
<Main data-testid="forgotPassword-checkEmail-main">
|
||||
<HorizontalGutter size="full">
|
||||
<Localized
|
||||
id="forgotPassword-checkEmail-receiveEmail"
|
||||
email={<UserEmail />}
|
||||
>
|
||||
<Typography variant="bodyCopy">
|
||||
{
|
||||
"If there is an account associated with <email></email>, you will receive an email with a link to create a new password."
|
||||
}
|
||||
</Typography>
|
||||
</Localized>
|
||||
<Localized id="forgotPassword-checkEmail-closeButton">
|
||||
<Button
|
||||
variant="filled"
|
||||
color="primary"
|
||||
size="large"
|
||||
fullWidth
|
||||
type="submit"
|
||||
onClick={closeWindow}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</Localized>
|
||||
</HorizontalGutter>
|
||||
</Main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CheckEmail;
|
||||
@@ -1,112 +1,14 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { Field, Form } from "react-final-form";
|
||||
import React, { FunctionComponent, useState } from "react";
|
||||
|
||||
import { Bar, Title } from "talk-auth/components//Header";
|
||||
import Main from "talk-auth/components/Main";
|
||||
import AutoHeightContainer from "talk-auth/containers/AutoHeightContainer";
|
||||
import { OnSubmit } from "talk-framework/lib/form";
|
||||
import {
|
||||
composeValidators,
|
||||
required,
|
||||
validateEmail,
|
||||
} from "talk-framework/lib/validation";
|
||||
import {
|
||||
Button,
|
||||
CallOut,
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
InputLabel,
|
||||
TextField,
|
||||
Typography,
|
||||
ValidationMessage,
|
||||
} from "talk-ui/components";
|
||||
import CheckEmail from "./CheckEmail";
|
||||
import ForgotPasswordForm from "./ForgotPasswordForm";
|
||||
|
||||
interface FormProps {
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface ForgotPasswordForm {
|
||||
onSubmit: OnSubmit<FormProps>;
|
||||
}
|
||||
|
||||
const ForgotPassword: StatelessComponent<ForgotPasswordForm> = props => {
|
||||
return (
|
||||
<div data-testid="forgotPassword-container">
|
||||
<Bar>
|
||||
<Localized id="forgotPassword-forgotPasswordHeader">
|
||||
<Title>Forgot Password</Title>
|
||||
</Localized>
|
||||
</Bar>
|
||||
<Main data-testid="forgotPassword-main">
|
||||
<Form onSubmit={props.onSubmit}>
|
||||
{({ handleSubmit, submitting, submitError }) => (
|
||||
<form autoComplete="off" onSubmit={handleSubmit}>
|
||||
<AutoHeightContainer />
|
||||
<HorizontalGutter size="full">
|
||||
<Localized id="forgotPassword-enterEmailAndGetALink">
|
||||
<Typography variant="bodyCopy">
|
||||
Enter your email address below and we will send you a link
|
||||
to reset your password.
|
||||
</Typography>
|
||||
</Localized>
|
||||
{submitError && (
|
||||
<CallOut color="error" fullWidth>
|
||||
{submitError}
|
||||
</CallOut>
|
||||
)}
|
||||
<Field
|
||||
name="email"
|
||||
validate={composeValidators(required, validateEmail)}
|
||||
>
|
||||
{({ input, meta }) => (
|
||||
<FormField>
|
||||
<Localized id="forgotPassword-emailAddressLabel">
|
||||
<InputLabel>Email Address</InputLabel>
|
||||
</Localized>
|
||||
<Localized
|
||||
id="forgotPassword-emailAddressTextField"
|
||||
attrs={{ placeholder: true }}
|
||||
>
|
||||
<TextField
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
value={input.value}
|
||||
placeholder="Email Address"
|
||||
color={
|
||||
meta.touched && (meta.error || meta.submitError)
|
||||
? "error"
|
||||
: "regular"
|
||||
}
|
||||
fullWidth
|
||||
disabled={submitting}
|
||||
/>
|
||||
</Localized>
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</FormField>
|
||||
)}
|
||||
</Field>
|
||||
<Localized id="forgotPassword-sendEmailButton">
|
||||
<Button
|
||||
variant="filled"
|
||||
color="primary"
|
||||
size="large"
|
||||
fullWidth
|
||||
disabled={submitting}
|
||||
>
|
||||
Send Email
|
||||
</Button>
|
||||
</Localized>
|
||||
</HorizontalGutter>
|
||||
</form>
|
||||
)}
|
||||
</Form>
|
||||
</Main>
|
||||
</div>
|
||||
const ForgotPassword: FunctionComponent = () => {
|
||||
const [checkEmail, setCheckEmail] = useState<string | null>(null);
|
||||
return checkEmail ? (
|
||||
<CheckEmail email={checkEmail} />
|
||||
) : (
|
||||
<ForgotPasswordForm onCheckEmail={setCheckEmail} />
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
import { FORM_ERROR } from "final-form";
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { FunctionComponent, useCallback } from "react";
|
||||
import { Field, Form } from "react-final-form";
|
||||
|
||||
import { Bar, SubBar, Title } from "talk-auth/components/Header";
|
||||
import Main from "talk-auth/components/Main";
|
||||
import AutoHeightContainer from "talk-auth/containers/AutoHeightContainer";
|
||||
import { getViewURL } from "talk-auth/helpers";
|
||||
import { ForgotPasswordMutation, SetViewMutation } from "talk-auth/mutations";
|
||||
import { InvalidRequestError } from "talk-framework/lib/errors";
|
||||
import { useMutation } from "talk-framework/lib/relay";
|
||||
import {
|
||||
composeValidators,
|
||||
required,
|
||||
validateEmail,
|
||||
} from "talk-framework/lib/validation";
|
||||
import {
|
||||
Button,
|
||||
CallOut,
|
||||
Flex,
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
InputLabel,
|
||||
TextField,
|
||||
TextLink,
|
||||
Typography,
|
||||
ValidationMessage,
|
||||
} from "talk-ui/components";
|
||||
|
||||
interface FormProps {
|
||||
email: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
onCheckEmail: (email: string) => void;
|
||||
}
|
||||
|
||||
const ForgotPasswordForm: FunctionComponent<Props> = ({ onCheckEmail }) => {
|
||||
const signInHref = getViewURL("SIGN_IN");
|
||||
const forgotPassword = useMutation(ForgotPasswordMutation);
|
||||
const setView = useMutation(SetViewMutation);
|
||||
const onSubmit = useCallback(
|
||||
async ({ email }: FormProps) => {
|
||||
try {
|
||||
await forgotPassword({
|
||||
email,
|
||||
});
|
||||
onCheckEmail(email);
|
||||
} catch (error) {
|
||||
if (error instanceof InvalidRequestError) {
|
||||
return error.invalidArgs;
|
||||
}
|
||||
return { [FORM_ERROR]: error.message };
|
||||
}
|
||||
return;
|
||||
},
|
||||
[forgotPassword]
|
||||
);
|
||||
const onGotoSignIn = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
setView({ view: "SIGN_IN", history: "push" });
|
||||
if (e.preventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
[setView]
|
||||
);
|
||||
|
||||
return (
|
||||
<div data-testid="forgotPassword-container">
|
||||
<Bar>
|
||||
<Localized id="forgotPassword-forgotPasswordHeader">
|
||||
<Title>Forgot Password?</Title>
|
||||
</Localized>
|
||||
</Bar>
|
||||
<SubBar>
|
||||
<Typography variant="bodyCopy" container={Flex}>
|
||||
<Localized id="forgotPassword-gotBackToSignIn">
|
||||
<TextLink href={signInHref} onClick={onGotoSignIn}>
|
||||
Go back to sign in page
|
||||
</TextLink>
|
||||
</Localized>
|
||||
</Typography>
|
||||
</SubBar>
|
||||
<Main data-testid="forgotPassword-main">
|
||||
<Form onSubmit={onSubmit}>
|
||||
{({ handleSubmit, submitting, submitError }) => (
|
||||
<form autoComplete="off" onSubmit={handleSubmit}>
|
||||
<AutoHeightContainer />
|
||||
<HorizontalGutter size="full">
|
||||
<Localized id="forgotPassword-enterEmailAndGetALink">
|
||||
<Typography variant="bodyCopy">
|
||||
Enter your email address below and we will send you a link
|
||||
to reset your password.
|
||||
</Typography>
|
||||
</Localized>
|
||||
{submitError && (
|
||||
<CallOut color="error" fullWidth>
|
||||
{submitError}
|
||||
</CallOut>
|
||||
)}
|
||||
<Field
|
||||
name="email"
|
||||
validate={composeValidators(required, validateEmail)}
|
||||
>
|
||||
{({ input, meta }) => (
|
||||
<FormField>
|
||||
<Localized id="forgotPassword-emailAddressLabel">
|
||||
<InputLabel htmlFor={input.name}>
|
||||
Email Address
|
||||
</InputLabel>
|
||||
</Localized>
|
||||
<Localized
|
||||
id="forgotPassword-emailAddressTextField"
|
||||
attrs={{ placeholder: true }}
|
||||
>
|
||||
<TextField
|
||||
id={input.name}
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
value={input.value}
|
||||
placeholder="Email Address"
|
||||
color={
|
||||
meta.touched && (meta.error || meta.submitError)
|
||||
? "error"
|
||||
: "regular"
|
||||
}
|
||||
fullWidth
|
||||
disabled={submitting}
|
||||
/>
|
||||
</Localized>
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</FormField>
|
||||
)}
|
||||
</Field>
|
||||
<Localized id="forgotPassword-sendEmailButton">
|
||||
<Button
|
||||
variant="filled"
|
||||
color="primary"
|
||||
size="large"
|
||||
fullWidth
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
>
|
||||
Send Email
|
||||
</Button>
|
||||
</Localized>
|
||||
</HorizontalGutter>
|
||||
</form>
|
||||
)}
|
||||
</Form>
|
||||
</Main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ForgotPasswordForm;
|
||||
@@ -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,63 +0,0 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { Form } from "react-final-form";
|
||||
import { OnSubmit } from "talk-framework/lib/form";
|
||||
|
||||
import { Bar, Title } from "talk-auth/components//Header";
|
||||
import ConfirmPasswordField from "talk-auth/components/ConfirmPasswordField";
|
||||
import Main from "talk-auth/components/Main";
|
||||
import SetPasswordField from "talk-auth/components/SetPasswordField";
|
||||
import AutoHeightContainer from "talk-auth/containers/AutoHeightContainer";
|
||||
import { Button, CallOut, HorizontalGutter } from "talk-ui/components";
|
||||
|
||||
interface FormProps {
|
||||
password: string;
|
||||
confirmPassword: string;
|
||||
}
|
||||
|
||||
export interface ResetPasswordForm {
|
||||
onSubmit: OnSubmit<FormProps>;
|
||||
}
|
||||
|
||||
const ResetPassword: StatelessComponent<ResetPasswordForm> = props => {
|
||||
return (
|
||||
<div data-testid="resetPassword-container">
|
||||
<Bar>
|
||||
<Localized id="resetPassword-resetPasswordHeader">
|
||||
<Title>Reset Password</Title>
|
||||
</Localized>
|
||||
</Bar>
|
||||
<Main data-testid="resetPassword-main">
|
||||
<Form onSubmit={props.onSubmit}>
|
||||
{({ handleSubmit, submitting, submitError }) => (
|
||||
<form autoComplete="off" onSubmit={handleSubmit}>
|
||||
<AutoHeightContainer />
|
||||
<HorizontalGutter size="full">
|
||||
{submitError && (
|
||||
<CallOut color="error" fullWidth>
|
||||
{submitError}
|
||||
</CallOut>
|
||||
)}
|
||||
<SetPasswordField disabled={submitting} />
|
||||
<ConfirmPasswordField disabled={submitting} />
|
||||
<Localized id="resetPassword-resetPasswordButton">
|
||||
<Button
|
||||
variant="filled"
|
||||
color="primary"
|
||||
size="large"
|
||||
fullWidth
|
||||
disabled={submitting}
|
||||
>
|
||||
Reset Password
|
||||
</Button>
|
||||
</Localized>
|
||||
</HorizontalGutter>
|
||||
</form>
|
||||
)}
|
||||
</Form>
|
||||
</Main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResetPassword;
|
||||
@@ -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;
|
||||
@@ -12,6 +12,7 @@ const SignInN = removeFragmentRefs(SignIn);
|
||||
it("renders correctly", () => {
|
||||
const props: PropTypesOf<typeof SignInN> = {
|
||||
onGotoSignUp: noop,
|
||||
signUpHref: "/signUp",
|
||||
emailEnabled: true,
|
||||
facebookEnabled: true,
|
||||
googleEnabled: true,
|
||||
@@ -27,6 +28,7 @@ it("renders correctly", () => {
|
||||
it("renders error", () => {
|
||||
const props: PropTypesOf<typeof SignInN> = {
|
||||
onGotoSignUp: noop,
|
||||
signUpHref: "/signUp",
|
||||
emailEnabled: true,
|
||||
facebookEnabled: true,
|
||||
googleEnabled: true,
|
||||
@@ -42,6 +44,7 @@ it("renders error", () => {
|
||||
it("renders without email login", () => {
|
||||
const props: PropTypesOf<typeof SignInN> = {
|
||||
onGotoSignUp: noop,
|
||||
signUpHref: "/signUp",
|
||||
emailEnabled: false,
|
||||
facebookEnabled: true,
|
||||
googleEnabled: true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { Bar, SubBar, Subtitle, Title } from "talk-auth/components//Header";
|
||||
import Main from "talk-auth/components/Main";
|
||||
@@ -7,10 +7,10 @@ import OrSeparator from "talk-auth/components/OrSeparator";
|
||||
import AutoHeightContainer from "talk-auth/containers/AutoHeightContainer";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import {
|
||||
Button,
|
||||
CallOut,
|
||||
Flex,
|
||||
HorizontalGutter,
|
||||
TextLink,
|
||||
Typography,
|
||||
} from "talk-ui/components";
|
||||
|
||||
@@ -21,7 +21,8 @@ import SignInWithOIDCContainer from "../containers/SignInWithOIDCContainer";
|
||||
|
||||
export interface SignInForm {
|
||||
error: string | null;
|
||||
onGotoSignUp: () => void;
|
||||
onGotoSignUp: React.EventHandler<React.MouseEvent>;
|
||||
signUpHref: string;
|
||||
emailEnabled?: boolean;
|
||||
facebookEnabled?: boolean;
|
||||
googleEnabled?: boolean;
|
||||
@@ -31,12 +32,13 @@ export interface SignInForm {
|
||||
PropTypesOf<typeof SignInWithGoogleContainer>["auth"];
|
||||
}
|
||||
|
||||
const SignIn: StatelessComponent<SignInForm> = ({
|
||||
const SignIn: FunctionComponent<SignInForm> = ({
|
||||
onGotoSignUp,
|
||||
emailEnabled,
|
||||
facebookEnabled,
|
||||
googleEnabled,
|
||||
oidcEnabled,
|
||||
signUpHref,
|
||||
auth,
|
||||
error,
|
||||
}) => {
|
||||
@@ -59,18 +61,10 @@ const SignIn: StatelessComponent<SignInForm> = ({
|
||||
<SubBar>
|
||||
<Localized
|
||||
id="signIn-noAccountSignUp"
|
||||
button={
|
||||
<Button
|
||||
data-testid="gotoSignUpButton"
|
||||
variant="underlined"
|
||||
size="small"
|
||||
color="primary"
|
||||
onClick={onGotoSignUp}
|
||||
/>
|
||||
}
|
||||
textlink={<TextLink onClick={onGotoSignUp} href={signUpHref} />}
|
||||
>
|
||||
<Typography variant="bodyCopy" container={Flex}>
|
||||
{"Don't have an account? <button>Sign Up</button>"}
|
||||
{"Don't have an account? <textlink>Sign Up</textlink>"}
|
||||
</Typography>
|
||||
</Localized>
|
||||
</SubBar>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import React, { FunctionComponent } from "react";
|
||||
import { Field, Form } from "react-final-form";
|
||||
import { OnSubmit } from "talk-framework/lib/form";
|
||||
|
||||
@@ -15,6 +15,8 @@ import {
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
InputLabel,
|
||||
TextLink,
|
||||
Typography,
|
||||
ValidationMessage,
|
||||
} from "talk-ui/components";
|
||||
|
||||
@@ -25,10 +27,11 @@ interface FormProps {
|
||||
|
||||
export interface SignInWithEmailForm {
|
||||
onSubmit: OnSubmit<FormProps>;
|
||||
onGotoForgotPassword: () => void;
|
||||
onGotoForgotPassword: React.EventHandler<React.MouseEvent>;
|
||||
forgotPasswordHref: string;
|
||||
}
|
||||
|
||||
const SignInWithEmail: StatelessComponent<SignInWithEmailForm> = props => {
|
||||
const SignInWithEmail: FunctionComponent<SignInWithEmailForm> = props => {
|
||||
return (
|
||||
<Form onSubmit={props.onSubmit}>
|
||||
{({ handleSubmit, submitting, submitError }) => (
|
||||
@@ -73,18 +76,16 @@ const SignInWithEmail: StatelessComponent<SignInWithEmailForm> = props => {
|
||||
</ValidationMessage>
|
||||
)}
|
||||
<Flex justifyContent="flex-end">
|
||||
<Localized id="signIn-forgotYourPassword">
|
||||
<Button
|
||||
data-testid="gotoForgotPasswordButton"
|
||||
variant="underlined"
|
||||
color="primary"
|
||||
size="small"
|
||||
disabled={submitting}
|
||||
onClick={props.onGotoForgotPassword}
|
||||
>
|
||||
Forgot your password?
|
||||
</Button>
|
||||
</Localized>
|
||||
<Typography variant="bodyCopy">
|
||||
<Localized id="signIn-forgotYourPassword">
|
||||
<TextLink
|
||||
onClick={props.onGotoForgotPassword}
|
||||
href={props.forgotPasswordHref}
|
||||
>
|
||||
Forgot your password?
|
||||
</TextLink>
|
||||
</Localized>
|
||||
</Typography>
|
||||
</Flex>
|
||||
</FormField>
|
||||
)}
|
||||
|
||||
@@ -16,16 +16,13 @@ exports[`renders correctly 1`] = `
|
||||
</Localized>
|
||||
<SubBar>
|
||||
<Localized
|
||||
button={
|
||||
<ForwardRef(forwardRef)
|
||||
color="primary"
|
||||
data-testid="gotoSignUpButton"
|
||||
id="signIn-noAccountSignUp"
|
||||
textlink={
|
||||
<withPropsOnChange(TextLinkProps)
|
||||
href="/signUp"
|
||||
onClick={[Function]}
|
||||
size="small"
|
||||
variant="underlined"
|
||||
/>
|
||||
}
|
||||
id="signIn-noAccountSignUp"
|
||||
>
|
||||
<ForwardRef(forwardRef)
|
||||
container={
|
||||
@@ -36,7 +33,7 @@ exports[`renders correctly 1`] = `
|
||||
}
|
||||
variant="bodyCopy"
|
||||
>
|
||||
Don't have an account? <button>Sign Up</button>
|
||||
Don't have an account? <textlink>Sign Up</textlink>
|
||||
</ForwardRef(forwardRef)>
|
||||
</Localized>
|
||||
</SubBar>
|
||||
@@ -46,7 +43,7 @@ exports[`renders correctly 1`] = `
|
||||
<ForwardRef(forwardRef)
|
||||
size="oneAndAHalf"
|
||||
>
|
||||
<withContext(createMutationContainer(withContext(createMutationContainer(SignInContainer)))) />
|
||||
<withContext(withMutation(withContext(createMutationContainer(SignInContainer)))) />
|
||||
<OrSeparator />
|
||||
<ForwardRef(forwardRef)>
|
||||
<Relay(SignInWithFacebookContainer)
|
||||
@@ -80,16 +77,13 @@ exports[`renders error 1`] = `
|
||||
</Localized>
|
||||
<SubBar>
|
||||
<Localized
|
||||
button={
|
||||
<ForwardRef(forwardRef)
|
||||
color="primary"
|
||||
data-testid="gotoSignUpButton"
|
||||
id="signIn-noAccountSignUp"
|
||||
textlink={
|
||||
<withPropsOnChange(TextLinkProps)
|
||||
href="/signUp"
|
||||
onClick={[Function]}
|
||||
size="small"
|
||||
variant="underlined"
|
||||
/>
|
||||
}
|
||||
id="signIn-noAccountSignUp"
|
||||
>
|
||||
<ForwardRef(forwardRef)
|
||||
container={
|
||||
@@ -100,7 +94,7 @@ exports[`renders error 1`] = `
|
||||
}
|
||||
variant="bodyCopy"
|
||||
>
|
||||
Don't have an account? <button>Sign Up</button>
|
||||
Don't have an account? <textlink>Sign Up</textlink>
|
||||
</ForwardRef(forwardRef)>
|
||||
</Localized>
|
||||
</SubBar>
|
||||
@@ -116,7 +110,7 @@ exports[`renders error 1`] = `
|
||||
>
|
||||
Server Error
|
||||
</withPropsOnChange(CallOut)>
|
||||
<withContext(createMutationContainer(withContext(createMutationContainer(SignInContainer)))) />
|
||||
<withContext(withMutation(withContext(createMutationContainer(SignInContainer)))) />
|
||||
<OrSeparator />
|
||||
<ForwardRef(forwardRef)>
|
||||
<Relay(SignInWithFacebookContainer)
|
||||
@@ -150,16 +144,13 @@ exports[`renders without email login 1`] = `
|
||||
</Localized>
|
||||
<SubBar>
|
||||
<Localized
|
||||
button={
|
||||
<ForwardRef(forwardRef)
|
||||
color="primary"
|
||||
data-testid="gotoSignUpButton"
|
||||
id="signIn-noAccountSignUp"
|
||||
textlink={
|
||||
<withPropsOnChange(TextLinkProps)
|
||||
href="/signUp"
|
||||
onClick={[Function]}
|
||||
size="small"
|
||||
variant="underlined"
|
||||
/>
|
||||
}
|
||||
id="signIn-noAccountSignUp"
|
||||
>
|
||||
<ForwardRef(forwardRef)
|
||||
container={
|
||||
@@ -170,7 +161,7 @@ exports[`renders without email login 1`] = `
|
||||
}
|
||||
variant="bodyCopy"
|
||||
>
|
||||
Don't have an account? <button>Sign Up</button>
|
||||
Don't have an account? <textlink>Sign Up</textlink>
|
||||
</ForwardRef(forwardRef)>
|
||||
</Localized>
|
||||
</SubBar>
|
||||
|
||||
@@ -7,27 +7,34 @@ import {
|
||||
SetViewMutation,
|
||||
SignInMutation,
|
||||
withClearErrorMutation,
|
||||
withSetViewMutation,
|
||||
withSignInMutation,
|
||||
} from "talk-auth/mutations";
|
||||
import {
|
||||
graphql,
|
||||
MutationProp,
|
||||
withFragmentContainer,
|
||||
withLocalStateContainer,
|
||||
withMutation,
|
||||
} from "talk-framework/lib/relay";
|
||||
|
||||
import { getViewURL } from "talk-auth/helpers";
|
||||
import SignIn from "../components/SignIn";
|
||||
|
||||
interface Props {
|
||||
local: LocalData;
|
||||
auth: AuthData;
|
||||
signIn: SignInMutation;
|
||||
setView: SetViewMutation;
|
||||
setView: MutationProp<typeof SetViewMutation>;
|
||||
clearError: ClearErrorMutation;
|
||||
}
|
||||
|
||||
class SignInContainer extends Component<Props> {
|
||||
private goToSignUp = () => this.props.setView({ view: "SIGN_UP" });
|
||||
private goToSignUp = (e: React.MouseEvent) => {
|
||||
this.props.setView({ view: "SIGN_UP", history: "push" });
|
||||
if (e.preventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.props.clearError();
|
||||
@@ -53,12 +60,13 @@ class SignInContainer extends Component<Props> {
|
||||
oidcEnabled={
|
||||
integrations.oidc.enabled && integrations.oidc.targetFilter.stream
|
||||
}
|
||||
signUpHref={getViewURL("SIGN_UP")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withSetViewMutation(
|
||||
const enhanced = withMutation(SetViewMutation)(
|
||||
withClearErrorMutation(
|
||||
withSignInMutation(
|
||||
withLocalStateContainer(
|
||||
|
||||
@@ -4,17 +4,18 @@ import React, { Component } from "react";
|
||||
import {
|
||||
SetViewMutation,
|
||||
SignInMutation,
|
||||
withSetViewMutation,
|
||||
withSignInMutation,
|
||||
} from "talk-auth/mutations";
|
||||
import { MutationProp, withMutation } from "talk-framework/lib/relay";
|
||||
|
||||
import { getViewURL } from "talk-auth/helpers";
|
||||
import SignInWithEmail, {
|
||||
SignInWithEmailForm,
|
||||
} from "../components/SignInWithEmail";
|
||||
|
||||
interface SignInContainerProps {
|
||||
signIn: SignInMutation;
|
||||
setView: SetViewMutation;
|
||||
setView: MutationProp<typeof SetViewMutation>;
|
||||
}
|
||||
|
||||
class SignInContainer extends Component<SignInContainerProps> {
|
||||
@@ -26,17 +27,24 @@ class SignInContainer extends Component<SignInContainerProps> {
|
||||
return { [FORM_ERROR]: error.message };
|
||||
}
|
||||
};
|
||||
private goToForgotPassword = () =>
|
||||
this.props.setView({ view: "FORGOT_PASSWORD" });
|
||||
private goToForgotPassword = (e: React.MouseEvent) => {
|
||||
this.props.setView({ view: "FORGOT_PASSWORD", history: "push" });
|
||||
if (e.preventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
public render() {
|
||||
return (
|
||||
<SignInWithEmail
|
||||
onSubmit={this.onSubmit}
|
||||
onGotoForgotPassword={this.goToForgotPassword}
|
||||
forgotPasswordHref={getViewURL("FORGOT_PASSWORD")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withSetViewMutation(withSignInMutation(SignInContainer));
|
||||
const enhanced = withMutation(SetViewMutation)(
|
||||
withSignInMutation(SignInContainer)
|
||||
);
|
||||
export default enhanced;
|
||||
|
||||
@@ -17,6 +17,7 @@ it("renders correctly", () => {
|
||||
googleEnabled: true,
|
||||
oidcEnabled: true,
|
||||
auth: {},
|
||||
signInHref: "/signIn",
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<SignUpN {...props} />);
|
||||
@@ -31,6 +32,7 @@ it("renders without email sign up", () => {
|
||||
googleEnabled: true,
|
||||
oidcEnabled: true,
|
||||
auth: {},
|
||||
signInHref: "/signIn",
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<SignUpN {...props} />);
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { Bar, SubBar, Subtitle, Title } from "talk-auth/components//Header";
|
||||
import Main from "talk-auth/components/Main";
|
||||
import OrSeparator from "talk-auth/components/OrSeparator";
|
||||
import AutoHeightContainer from "talk-auth/containers/AutoHeightContainer";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { Button, Flex, HorizontalGutter, Typography } from "talk-ui/components";
|
||||
import {
|
||||
Flex,
|
||||
HorizontalGutter,
|
||||
TextLink,
|
||||
Typography,
|
||||
} from "talk-ui/components";
|
||||
|
||||
import SignUpWithEmailContainer from "../containers/SignUpWithEmailContainer";
|
||||
import SignUpWithFacebookContainer from "../containers/SignUpWithFacebookContainer";
|
||||
@@ -14,7 +19,8 @@ import SignUpWithGoogleContainer from "../containers/SignUpWithGoogleContainer";
|
||||
import SignUpWithOIDCContainer from "../containers/SignUpWithOIDCContainer";
|
||||
|
||||
interface Props {
|
||||
onGotoSignIn: () => void;
|
||||
onGotoSignIn: React.EventHandler<React.MouseEvent>;
|
||||
signInHref: string;
|
||||
emailEnabled?: boolean;
|
||||
facebookEnabled?: boolean;
|
||||
googleEnabled?: boolean;
|
||||
@@ -24,12 +30,13 @@ interface Props {
|
||||
PropTypesOf<typeof SignUpWithGoogleContainer>["auth"];
|
||||
}
|
||||
|
||||
const SignUp: StatelessComponent<Props> = ({
|
||||
const SignUp: FunctionComponent<Props> = ({
|
||||
onGotoSignIn,
|
||||
emailEnabled,
|
||||
facebookEnabled,
|
||||
googleEnabled,
|
||||
oidcEnabled,
|
||||
signInHref,
|
||||
auth,
|
||||
}) => {
|
||||
const oneClickUptegrationEnabled =
|
||||
@@ -51,18 +58,10 @@ const SignUp: StatelessComponent<Props> = ({
|
||||
<SubBar>
|
||||
<Localized
|
||||
id="signUp-accountAvailableSignIn"
|
||||
button={
|
||||
<Button
|
||||
data-testid="gotoSignInButton"
|
||||
variant="underlined"
|
||||
size="small"
|
||||
color="primary"
|
||||
onClick={onGotoSignIn}
|
||||
/>
|
||||
}
|
||||
textlink={<TextLink onClick={onGotoSignIn} href={signInHref} />}
|
||||
>
|
||||
<Typography variant="bodyCopy" container={Flex}>
|
||||
{"Already have an account? <button>Sign In</button>"}
|
||||
{"Already have an account? <textlink>Sign In</textlink>"}
|
||||
</Typography>
|
||||
</Localized>
|
||||
</SubBar>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import * as React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
import { FunctionComponent } from "react";
|
||||
import { Form } from "react-final-form";
|
||||
|
||||
import EmailField from "talk-auth/components/EmailField";
|
||||
@@ -27,7 +27,7 @@ interface Props {
|
||||
onSubmit: OnSubmit<FormProps>;
|
||||
}
|
||||
|
||||
const SignUp: StatelessComponent<Props> = props => {
|
||||
const SignUp: FunctionComponent<Props> = props => {
|
||||
return (
|
||||
<Form onSubmit={props.onSubmit}>
|
||||
{({ handleSubmit, submitting, submitError }) => (
|
||||
|
||||
@@ -16,16 +16,13 @@ exports[`renders correctly 1`] = `
|
||||
</Localized>
|
||||
<SubBar>
|
||||
<Localized
|
||||
button={
|
||||
<ForwardRef(forwardRef)
|
||||
color="primary"
|
||||
data-testid="gotoSignInButton"
|
||||
id="signUp-accountAvailableSignIn"
|
||||
textlink={
|
||||
<withPropsOnChange(TextLinkProps)
|
||||
href="/signIn"
|
||||
onClick={[Function]}
|
||||
size="small"
|
||||
variant="underlined"
|
||||
/>
|
||||
}
|
||||
id="signUp-accountAvailableSignIn"
|
||||
>
|
||||
<ForwardRef(forwardRef)
|
||||
container={
|
||||
@@ -36,7 +33,7 @@ exports[`renders correctly 1`] = `
|
||||
}
|
||||
variant="bodyCopy"
|
||||
>
|
||||
Already have an account? <button>Sign In</button>
|
||||
Already have an account? <textlink>Sign In</textlink>
|
||||
</ForwardRef(forwardRef)>
|
||||
</Localized>
|
||||
</SubBar>
|
||||
@@ -80,16 +77,13 @@ exports[`renders without email sign up 1`] = `
|
||||
</Localized>
|
||||
<SubBar>
|
||||
<Localized
|
||||
button={
|
||||
<ForwardRef(forwardRef)
|
||||
color="primary"
|
||||
data-testid="gotoSignInButton"
|
||||
id="signUp-accountAvailableSignIn"
|
||||
textlink={
|
||||
<withPropsOnChange(TextLinkProps)
|
||||
href="/signIn"
|
||||
onClick={[Function]}
|
||||
size="small"
|
||||
variant="underlined"
|
||||
/>
|
||||
}
|
||||
id="signUp-accountAvailableSignIn"
|
||||
>
|
||||
<ForwardRef(forwardRef)
|
||||
container={
|
||||
@@ -100,7 +94,7 @@ exports[`renders without email sign up 1`] = `
|
||||
}
|
||||
variant="bodyCopy"
|
||||
>
|
||||
Already have an account? <button>Sign In</button>
|
||||
Already have an account? <textlink>Sign In</textlink>
|
||||
</ForwardRef(forwardRef)>
|
||||
</Localized>
|
||||
</SubBar>
|
||||
|
||||
@@ -1,22 +1,34 @@
|
||||
import React, { Component } from "react";
|
||||
|
||||
import { SignUpContainer_auth as AuthData } from "talk-auth/__generated__/SignUpContainer_auth.graphql";
|
||||
import { SetViewMutation, withSetViewMutation } from "talk-auth/mutations";
|
||||
import { graphql, withFragmentContainer } from "talk-framework/lib/relay";
|
||||
import { SetViewMutation } from "talk-auth/mutations";
|
||||
import {
|
||||
graphql,
|
||||
MutationProp,
|
||||
withFragmentContainer,
|
||||
withMutation,
|
||||
} from "talk-framework/lib/relay";
|
||||
|
||||
import { getViewURL } from "talk-auth/helpers";
|
||||
import SignUp from "../components/SignUp";
|
||||
|
||||
interface Props {
|
||||
auth: AuthData;
|
||||
setView: SetViewMutation;
|
||||
setView: MutationProp<typeof SetViewMutation>;
|
||||
}
|
||||
|
||||
class SignUpContainer extends Component<Props> {
|
||||
private goToSignIn = () => this.props.setView({ view: "SIGN_IN" });
|
||||
private goToSignIn = (e: React.MouseEvent) => {
|
||||
this.props.setView({ view: "SIGN_IN", history: "push" });
|
||||
if (e.preventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
public render() {
|
||||
const integrations = this.props.auth.integrations;
|
||||
return (
|
||||
<SignUp
|
||||
signInHref={getViewURL("SIGN_IN")}
|
||||
auth={this.props.auth}
|
||||
onGotoSignIn={this.goToSignIn}
|
||||
emailEnabled={
|
||||
@@ -44,7 +56,7 @@ class SignUpContainer extends Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withSetViewMutation(
|
||||
const enhanced = withMutation(SetViewMutation)(
|
||||
withFragmentContainer<Props>({
|
||||
auth: graphql`
|
||||
fragment SignUpContainer_auth on Auth {
|
||||
|
||||
Reference in New Issue
Block a user