This commit is contained in:
dominik-zeglen 2019-09-02 21:23:37 +02:00
parent b582a7f51a
commit 48ca3b9e15
18 changed files with 225 additions and 50 deletions

View file

@ -9,15 +9,13 @@ import { User } from "./types/User";
import { VerifyToken, VerifyTokenVariables } from "./types/VerifyToken";
interface AuthProviderOperationsProps {
children: (
props: {
hasToken: boolean;
isAuthenticated: boolean;
tokenAuthLoading: boolean;
tokenVerifyLoading: boolean;
user: User;
}
) => React.ReactNode;
children: (props: {
hasToken: boolean;
isAuthenticated: boolean;
tokenAuthLoading: boolean;
tokenVerifyLoading: boolean;
user: User;
}) => React.ReactNode;
}
const AuthProviderOperations: React.StatelessComponent<
AuthProviderOperationsProps
@ -41,15 +39,13 @@ const AuthProviderOperations: React.StatelessComponent<
};
interface AuthProviderProps {
children: (
props: {
hasToken: boolean;
isAuthenticated: boolean;
tokenAuthLoading: boolean;
tokenVerifyLoading: boolean;
user: User;
}
) => React.ReactNode;
children: (props: {
hasToken: boolean;
isAuthenticated: boolean;
tokenAuthLoading: boolean;
tokenVerifyLoading: boolean;
user: User;
}) => React.ReactNode;
tokenAuth: PartialMutationProviderOutput<TokenAuth, TokenAuthVariables>;
tokenVerify: PartialMutationProviderOutput<VerifyToken, VerifyTokenVariables>;
}
@ -116,9 +112,16 @@ class AuthProvider extends React.Component<
const { children, tokenAuth, tokenVerify } = this.props;
const { user } = this.state;
const isAuthenticated = !!user;
return (
<UserContext.Provider
value={{ user, login: this.login, logout: this.logout }}
value={{
login: this.login,
logout: this.logout,
tokenAuthLoading: tokenAuth.opts.loading,
tokenVerifyLoading: tokenVerify.opts.loading,
user
}}
>
{children({
hasToken: !!getAuthToken(),

View file

@ -56,7 +56,13 @@ export interface LoginCardProps extends WithStyles<typeof styles> {
}
const LoginCard = withStyles(styles, { name: "LoginCard" })(
({ classes, error, disableLoginButton, onSubmit }: LoginCardProps) => {
({
classes,
error,
disableLoginButton,
onPasswordRecovery,
onSubmit
}: LoginCardProps) => {
const intl = useIntl();
return (
@ -123,7 +129,7 @@ const LoginCard = withStyles(styles, { name: "LoginCard" })(
</Button>
</div>
<FormSpacer />
<Typography className={classes.link}>
<Typography className={classes.link} onClick={onPasswordRecovery}>
<FormattedMessage
defaultMessage="Reset your password"
description="button"

View file

@ -6,4 +6,9 @@ import NewPasswordPage from "./NewPasswordPage";
storiesOf("Views / Authentication / Set up a new password", module)
.addDecorator(Decorator)
.add("default", () => <NewPasswordPage onSubmit={() => undefined} />);
.add("default", () => (
<NewPasswordPage disabled={false} onSubmit={() => undefined} />
))
.add("loading", () => (
<NewPasswordPage disabled={true} onSubmit={() => undefined} />
));

View file

@ -25,6 +25,7 @@ export interface NewPasswordPageFormData {
confirmPassword: string;
}
export interface NewPasswordPageProps {
disabled: boolean;
onSubmit: (data: NewPasswordPageFormData) => void;
}
@ -34,7 +35,7 @@ const initialForm: NewPasswordPageFormData = {
};
const NewPasswordPage: React.FC<NewPasswordPageProps> = props => {
const { onSubmit } = props;
const { disabled, onSubmit } = props;
const classes = useStyles(props);
const intl = useIntl();
@ -55,6 +56,7 @@ const NewPasswordPage: React.FC<NewPasswordPageProps> = props => {
autoFocus
fullWidth
autoComplete="none"
disabled={disabled}
label={intl.formatMessage({
defaultMessage: "New Password"
})}
@ -72,6 +74,7 @@ const NewPasswordPage: React.FC<NewPasswordPageProps> = props => {
fullWidth
error={passwordError}
autoComplete="none"
disabled={disabled}
label={intl.formatMessage({
defaultMessage: "Confirm Password"
})}
@ -93,7 +96,7 @@ const NewPasswordPage: React.FC<NewPasswordPageProps> = props => {
<Button
className={classes.submit}
color="primary"
disabled={passwordError && data.password.length > 0}
disabled={(passwordError && data.password.length > 0) || disabled}
variant="contained"
onClick={handleSubmit}
type="submit"

View file

@ -0,0 +1,2 @@
export { default } from "./NewPasswordPage";
export * from "./NewPasswordPage";

View file

@ -6,4 +6,9 @@ import ResetPasswordPage from "./ResetPasswordPage";
storiesOf("Views / Authentication / Reset password", module)
.addDecorator(Decorator)
.add("default", () => <ResetPasswordPage onSubmit={() => undefined} />);
.add("default", () => (
<ResetPasswordPage disabled={false} onSubmit={() => undefined} />
))
.add("loading", () => (
<ResetPasswordPage disabled={true} onSubmit={() => undefined} />
));

View file

@ -25,11 +25,12 @@ export interface ResetPasswordPageFormData {
email: string;
}
export interface ResetPasswordPageProps {
disabled: boolean;
onSubmit: (data: ResetPasswordPageFormData) => void;
}
const ResetPasswordPage: React.FC<ResetPasswordPageProps> = props => {
const { onSubmit } = props;
const { disabled, onSubmit } = props;
const classes = useStyles(props);
const intl = useIntl();
@ -44,6 +45,7 @@ const ResetPasswordPage: React.FC<ResetPasswordPageProps> = props => {
<FormSpacer />
<TextField
autoFocus
disabled={disabled}
fullWidth
autoComplete="username"
label={intl.formatMessage(commonMessages.email)}
@ -58,6 +60,7 @@ const ResetPasswordPage: React.FC<ResetPasswordPageProps> = props => {
<Button
className={classes.submit}
color="primary"
disabled={disabled}
variant="contained"
onClick={handleSubmit}
type="submit"

View file

@ -0,0 +1,2 @@
export { default } from "./ResetPasswordPage";
export * from "./ResetPasswordPage";

View file

@ -0,0 +1,2 @@
export { default } from "./ResetPasswordSuccessPage";
export * from "./ResetPasswordSuccessPage";

View file

@ -1,18 +1,32 @@
import React from "react";
import { Route, Switch } from "react-router-dom";
import { User } from "./types/User";
import {
newPasswordPath,
passwordResetPath,
passwordResetSuccessPath
} from "./urls";
import LoginView from "./views/Login";
import NewPassword from "./views/NewPassword";
import ResetPassword from "./views/ResetPassword";
import ResetPasswordSuccess from "./views/ResetPasswordSuccess";
const TOKEN_STORAGE_KEY = "dashboardAuth";
interface UserContext {
login: (username: string, password: string, persist: boolean) => void;
logout: () => void;
tokenAuthLoading: boolean;
tokenVerifyLoading: boolean;
user?: User;
}
export const UserContext = React.createContext<UserContext>({
login: undefined,
logout: undefined
logout: undefined,
tokenAuthLoading: false,
tokenVerifyLoading: false
});
export const getAuthToken = () =>
@ -28,3 +42,15 @@ export const removeAuthToken = () => {
localStorage.removeItem(TOKEN_STORAGE_KEY);
sessionStorage.removeItem(TOKEN_STORAGE_KEY);
};
const AuthRouter: React.FC = () => (
<Switch>
<Route path={passwordResetPath} component={ResetPassword} />
<Route path={passwordResetSuccessPath} component={ResetPasswordSuccess} />
<Route path={newPasswordPath} component={NewPassword} />
<Route component={LoginView} />
</Switch>
);
AuthRouter.displayName = "AuthRouter";
export default AuthRouter;

View file

@ -1,6 +1,10 @@
import gql from "graphql-tag";
import { TypedMutation } from "../mutations";
import {
RequestPasswordReset,
RequestPasswordResetVariables
} from "./types/RequestPasswordReset";
import { TokenAuth, TokenAuthVariables } from "./types/TokenAuth";
import { VerifyToken, VerifyTokenVariables } from "./types/VerifyToken";
@ -59,3 +63,18 @@ export const TypedVerifyTokenMutation = TypedMutation<
VerifyToken,
VerifyTokenVariables
>(tokenVerifyMutation);
export const requestPasswordReset = gql`
mutation RequestPasswordReset($email: String!, $redirectUrl: String!) {
requestPasswordReset(email: $email, redirectUrl: $redirectUrl) {
errors {
field
message
}
}
}
`;
export const RequestPasswordResetMutation = TypedMutation<
RequestPasswordReset,
RequestPasswordResetVariables
>(requestPasswordReset);

View file

@ -0,0 +1,27 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
// ====================================================
// GraphQL mutation operation: RequestPasswordReset
// ====================================================
export interface RequestPasswordReset_requestPasswordReset_errors {
__typename: "Error";
field: string | null;
message: string | null;
}
export interface RequestPasswordReset_requestPasswordReset {
__typename: "RequestPasswordReset";
errors: RequestPasswordReset_requestPasswordReset_errors[] | null;
}
export interface RequestPasswordReset {
requestPasswordReset: RequestPasswordReset_requestPasswordReset | null;
}
export interface RequestPasswordResetVariables {
email: string;
redirectUrl: string;
}

17
src/auth/urls.ts Normal file
View file

@ -0,0 +1,17 @@
import { stringify as stringifyQs } from "qs";
export const loginPath = "/login/";
export const loginUrl = loginPath;
export const passwordResetPath = "/reset-password/";
export const passwordResetUrl = passwordResetPath;
export const passwordResetSuccessPath = "/reset-password/success/";
export const passwordResetSuccessUrl = passwordResetSuccessPath;
export const newPasswordPath = "/new-password/";
export interface NewPasswordUrlQueryParams {
token: string;
}
export const newPasswordUrl = (params?: NewPasswordUrlQueryParams) =>
newPasswordPath + "?" + stringifyQs(params);

View file

@ -1,27 +1,25 @@
import React from "react";
import useNavigator from "@saleor/hooks/useNavigator";
import useUser from "@saleor/hooks/useUser";
import LoginPage, { FormData } from "../components/LoginPage";
import { UserContext } from "../index";
import { passwordResetUrl } from "../urls";
interface LoginViewProps {
loading: boolean;
}
const LoginView: React.FC = () => {
const navigate = useNavigator();
const { login, user, tokenAuthLoading } = useUser();
const LoginView: React.StatelessComponent<LoginViewProps> = ({ loading }) => (
<UserContext.Consumer>
{({ login, user }) => {
const handleSubmit = (data: FormData) =>
login(data.email, data.password, data.rememberMe);
return (
<LoginPage
error={user === null}
disableLoginButton={loading}
onPasswordRecovery={undefined}
onSubmit={handleSubmit}
/>
);
}}
</UserContext.Consumer>
);
const handleSubmit = (data: FormData) =>
login(data.email, data.password, data.rememberMe);
return (
<LoginPage
error={user === null}
disableLoginButton={tokenAuthLoading}
onPasswordRecovery={() => navigate(passwordResetUrl)}
onSubmit={handleSubmit}
/>
);
};
LoginView.displayName = "LoginView";
export default LoginView;

View file

View file

@ -0,0 +1,45 @@
import React from "react";
import urlJoin from "url-join";
import useNavigator from "@saleor/hooks/useNavigator";
import useShop from "@saleor/hooks/useShop";
import ResetPasswordPage, {
ResetPasswordPageFormData
} from "../components/ResetPasswordPage";
import { RequestPasswordResetMutation } from "../mutations";
import { RequestPasswordReset } from "../types/RequestPasswordReset";
import { newPasswordUrl, passwordResetSuccessUrl } from "../urls";
const ResetPasswordView: React.FC = () => {
const navigate = useNavigator();
const shop = useShop();
const handleRequestPasswordReset = (data: RequestPasswordReset) => {
if (data.requestPasswordReset.errors.length === 0) {
navigate(passwordResetSuccessUrl);
}
};
return (
<RequestPasswordResetMutation onCompleted={handleRequestPasswordReset}>
{(requestPasswordReset, requestPasswordResetOpts) => {
const handleSubmit = (data: ResetPasswordPageFormData) =>
requestPasswordReset({
variables: {
email: data.email,
redirectUrl: urlJoin(shop.domain.url, newPasswordUrl())
}
});
return (
<ResetPasswordPage
disabled={requestPasswordResetOpts.loading}
onSubmit={handleSubmit}
/>
);
}}
</RequestPasswordResetMutation>
);
};
ResetPasswordView.displayName = "ResetPasswordView";
export default ResetPasswordView;

View file

@ -0,0 +1,13 @@
import React from "react";
import useNavigator from "@saleor/hooks/useNavigator";
import ResetPasswordSuccessPage from "../components/ResetPasswordSuccessPage";
import { loginUrl } from "../urls";
const ResetPasswordSuccessView: React.FC = () => {
const navigate = useNavigator();
return <ResetPasswordSuccessPage onBack={() => navigate(loginUrl)} />;
};
ResetPasswordSuccessView.displayName = "ResetPasswordSuccessView";
export default ResetPasswordSuccessView;

View file

@ -13,12 +13,11 @@ import { BrowserRouter, Route, Switch } from "react-router-dom";
import AttributeSection from "./attributes";
import { attributeSection } from "./attributes/urls";
import { getAuthToken, removeAuthToken } from "./auth";
import Auth, { getAuthToken, removeAuthToken } from "./auth";
import AuthProvider from "./auth/AuthProvider";
import LoginLoading from "./auth/components/LoginLoading/LoginLoading";
import SectionRoute from "./auth/components/SectionRoute";
import { hasPermission } from "./auth/misc";
import Login from "./auth/views/Login";
import CategorySection from "./categories";
import CollectionSection from "./collections";
import { AppProgressProvider } from "./components/AppProgress";
@ -241,7 +240,7 @@ const Routes: React.FC = () => {
) : hasToken && tokenVerifyLoading ? (
<LoginLoading />
) : (
<Login loading={tokenAuthLoading} />
<Auth />
)
}
</AuthProvider>