Make password reset work
This commit is contained in:
parent
48ca3b9e15
commit
2cb559b537
14 changed files with 216 additions and 64 deletions
|
@ -1,12 +1,12 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { getMutationProviderData } from "../misc";
|
import { MutationFunction, MutationResult } from "react-apollo";
|
||||||
import { PartialMutationProviderOutput } from "../types";
|
import { UserContext } from "./";
|
||||||
import { getAuthToken, removeAuthToken, setAuthToken, UserContext } from "./";
|
|
||||||
import { TypedTokenAuthMutation, TypedVerifyTokenMutation } from "./mutations";
|
import { TypedTokenAuthMutation, TypedVerifyTokenMutation } from "./mutations";
|
||||||
import { TokenAuth, TokenAuthVariables } from "./types/TokenAuth";
|
import { TokenAuth, TokenAuthVariables } from "./types/TokenAuth";
|
||||||
import { User } from "./types/User";
|
import { User } from "./types/User";
|
||||||
import { VerifyToken, VerifyTokenVariables } from "./types/VerifyToken";
|
import { VerifyToken, VerifyTokenVariables } from "./types/VerifyToken";
|
||||||
|
import { getAuthToken, removeAuthToken, setAuthToken } from "./utils";
|
||||||
|
|
||||||
interface AuthProviderOperationsProps {
|
interface AuthProviderOperationsProps {
|
||||||
children: (props: {
|
children: (props: {
|
||||||
|
@ -25,10 +25,7 @@ const AuthProviderOperations: React.StatelessComponent<
|
||||||
{(...tokenAuth) => (
|
{(...tokenAuth) => (
|
||||||
<TypedVerifyTokenMutation>
|
<TypedVerifyTokenMutation>
|
||||||
{(...tokenVerify) => (
|
{(...tokenVerify) => (
|
||||||
<AuthProvider
|
<AuthProvider tokenAuth={tokenAuth} tokenVerify={tokenVerify}>
|
||||||
tokenAuth={getMutationProviderData(...tokenAuth)}
|
|
||||||
tokenVerify={getMutationProviderData(...tokenVerify)}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
)}
|
)}
|
||||||
|
@ -46,8 +43,14 @@ interface AuthProviderProps {
|
||||||
tokenVerifyLoading: boolean;
|
tokenVerifyLoading: boolean;
|
||||||
user: User;
|
user: User;
|
||||||
}) => React.ReactNode;
|
}) => React.ReactNode;
|
||||||
tokenAuth: PartialMutationProviderOutput<TokenAuth, TokenAuthVariables>;
|
tokenAuth: [
|
||||||
tokenVerify: PartialMutationProviderOutput<VerifyToken, VerifyTokenVariables>;
|
MutationFunction<TokenAuth, TokenAuthVariables>,
|
||||||
|
MutationResult<TokenAuth>
|
||||||
|
];
|
||||||
|
tokenVerify: [
|
||||||
|
MutationFunction<VerifyToken, VerifyTokenVariables>,
|
||||||
|
MutationResult<VerifyToken>
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AuthProviderState {
|
interface AuthProviderState {
|
||||||
|
@ -66,23 +69,26 @@ class AuthProvider extends React.Component<
|
||||||
|
|
||||||
componentWillReceiveProps(props: AuthProviderProps) {
|
componentWillReceiveProps(props: AuthProviderProps) {
|
||||||
const { tokenAuth, tokenVerify } = props;
|
const { tokenAuth, tokenVerify } = props;
|
||||||
if (tokenAuth.opts.error || tokenVerify.opts.error) {
|
const tokenAuthOpts = tokenAuth[1];
|
||||||
|
const tokenVerifyOpts = tokenVerify[1];
|
||||||
|
|
||||||
|
if (tokenAuthOpts.error || tokenVerifyOpts.error) {
|
||||||
this.logout();
|
this.logout();
|
||||||
}
|
}
|
||||||
if (tokenAuth.opts.data) {
|
if (tokenAuthOpts.data) {
|
||||||
const user = tokenAuth.opts.data.tokenCreate.user;
|
const user = tokenAuthOpts.data.tokenCreate.user;
|
||||||
// FIXME: Now we set state also when auth fails and returned user is
|
// FIXME: Now we set state also when auth fails and returned user is
|
||||||
// `null`, because the LoginView uses this `null` to display error.
|
// `null`, because the LoginView uses this `null` to display error.
|
||||||
this.setState({ user });
|
this.setState({ user });
|
||||||
if (user) {
|
if (user) {
|
||||||
setAuthToken(
|
setAuthToken(
|
||||||
tokenAuth.opts.data.tokenCreate.token,
|
tokenAuthOpts.data.tokenCreate.token,
|
||||||
this.state.persistToken
|
this.state.persistToken
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (tokenVerify.opts.data && tokenVerify.opts.data.tokenVerify.user) {
|
if (tokenVerifyOpts.data && tokenVerifyOpts.data.tokenVerify.user) {
|
||||||
const user = tokenVerify.opts.data.tokenVerify.user;
|
const user = tokenVerifyOpts.data.tokenVerify.user;
|
||||||
this.setState({ user });
|
this.setState({ user });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,17 +96,23 @@ class AuthProvider extends React.Component<
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { user } = this.state;
|
const { user } = this.state;
|
||||||
const { tokenVerify } = this.props;
|
|
||||||
const token = getAuthToken();
|
const token = getAuthToken();
|
||||||
if (!!token && !user) {
|
if (!!token && !user) {
|
||||||
tokenVerify.mutate({ token });
|
this.verifyToken(token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
login = (email: string, password: string, persistToken: boolean) => {
|
login = (email: string, password: string, persistToken: boolean) => {
|
||||||
const { tokenAuth } = this.props;
|
const { tokenAuth } = this.props;
|
||||||
|
const [tokenAuthFn] = tokenAuth;
|
||||||
|
|
||||||
this.setState({ persistToken });
|
this.setState({ persistToken });
|
||||||
tokenAuth.mutate({ email, password });
|
tokenAuthFn({ variables: { email, password } });
|
||||||
|
};
|
||||||
|
|
||||||
|
loginByToken = (token: string, user: User) => {
|
||||||
|
this.setState({ user });
|
||||||
|
setAuthToken(token, this.state.persistToken);
|
||||||
};
|
};
|
||||||
|
|
||||||
logout = () => {
|
logout = () => {
|
||||||
|
@ -108,8 +120,17 @@ class AuthProvider extends React.Component<
|
||||||
removeAuthToken();
|
removeAuthToken();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
verifyToken = (token: string) => {
|
||||||
|
const { tokenVerify } = this.props;
|
||||||
|
const [tokenVerifyFn] = tokenVerify;
|
||||||
|
|
||||||
|
return tokenVerifyFn({ variables: { token } });
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { children, tokenAuth, tokenVerify } = this.props;
|
const { children, tokenAuth, tokenVerify } = this.props;
|
||||||
|
const tokenAuthOpts = tokenAuth[1];
|
||||||
|
const tokenVerifyOpts = tokenVerify[1];
|
||||||
const { user } = this.state;
|
const { user } = this.state;
|
||||||
const isAuthenticated = !!user;
|
const isAuthenticated = !!user;
|
||||||
|
|
||||||
|
@ -117,17 +138,18 @@ class AuthProvider extends React.Component<
|
||||||
<UserContext.Provider
|
<UserContext.Provider
|
||||||
value={{
|
value={{
|
||||||
login: this.login,
|
login: this.login,
|
||||||
|
loginByToken: this.loginByToken,
|
||||||
logout: this.logout,
|
logout: this.logout,
|
||||||
tokenAuthLoading: tokenAuth.opts.loading,
|
tokenAuthLoading: tokenAuthOpts.loading,
|
||||||
tokenVerifyLoading: tokenVerify.opts.loading,
|
tokenVerifyLoading: tokenVerifyOpts.loading,
|
||||||
user
|
user
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children({
|
{children({
|
||||||
hasToken: !!getAuthToken(),
|
hasToken: !!getAuthToken(),
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
tokenAuthLoading: tokenAuth.opts.loading,
|
tokenAuthLoading: tokenAuthOpts.loading,
|
||||||
tokenVerifyLoading: tokenVerify.opts.loading,
|
tokenVerifyLoading: tokenVerifyOpts.loading,
|
||||||
user
|
user
|
||||||
})}
|
})}
|
||||||
</UserContext.Provider>
|
</UserContext.Provider>
|
||||||
|
|
|
@ -9,7 +9,6 @@ import TextField from "@material-ui/core/TextField";
|
||||||
import Typography from "@material-ui/core/Typography";
|
import Typography from "@material-ui/core/Typography";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import Layout from "../Layout";
|
|
||||||
|
|
||||||
import { ControlledCheckbox } from "@saleor/components/ControlledCheckbox";
|
import { ControlledCheckbox } from "@saleor/components/ControlledCheckbox";
|
||||||
import Form from "@saleor/components/Form";
|
import Form from "@saleor/components/Form";
|
||||||
|
@ -71,7 +70,7 @@ const LoginCard = withStyles(styles, { name: "LoginCard" })(
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
>
|
>
|
||||||
{({ change: handleChange, data, submit: handleSubmit }) => (
|
{({ change: handleChange, data, submit: handleSubmit }) => (
|
||||||
<Layout>
|
<>
|
||||||
{error && (
|
{error && (
|
||||||
<div className={classes.panel}>
|
<div className={classes.panel}>
|
||||||
<Typography variant="caption">
|
<Typography variant="caption">
|
||||||
|
@ -135,7 +134,7 @@ const LoginCard = withStyles(styles, { name: "LoginCard" })(
|
||||||
description="button"
|
description="button"
|
||||||
/>
|
/>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Layout>
|
</>
|
||||||
)}
|
)}
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,7 +7,6 @@ import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import Form from "@saleor/components/Form";
|
import Form from "@saleor/components/Form";
|
||||||
import FormSpacer from "@saleor/components/FormSpacer";
|
import FormSpacer from "@saleor/components/FormSpacer";
|
||||||
import Layout from "../Layout";
|
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
const useStyles = makeStyles(
|
||||||
{
|
{
|
||||||
|
@ -47,7 +46,7 @@ const NewPasswordPage: React.FC<NewPasswordPageProps> = props => {
|
||||||
data.password !== data.confirmPassword && data.password.length > 0;
|
data.password !== data.confirmPassword && data.password.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<>
|
||||||
<Typography>
|
<Typography>
|
||||||
<FormattedMessage defaultMessage="Please set up a new password." />
|
<FormattedMessage defaultMessage="Please set up a new password." />
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@ -106,7 +105,7 @@ const NewPasswordPage: React.FC<NewPasswordPageProps> = props => {
|
||||||
description="button"
|
description="button"
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</Layout>
|
</>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</Form>
|
</Form>
|
||||||
|
|
|
@ -8,7 +8,6 @@ import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import Form from "@saleor/components/Form";
|
import Form from "@saleor/components/Form";
|
||||||
import FormSpacer from "@saleor/components/FormSpacer";
|
import FormSpacer from "@saleor/components/FormSpacer";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
import Layout from "../Layout";
|
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
const useStyles = makeStyles(
|
||||||
{
|
{
|
||||||
|
@ -38,7 +37,7 @@ const ResetPasswordPage: React.FC<ResetPasswordPageProps> = props => {
|
||||||
return (
|
return (
|
||||||
<Form initial={{ email: "" }} onSubmit={onSubmit}>
|
<Form initial={{ email: "" }} onSubmit={onSubmit}>
|
||||||
{({ change: handleChange, data, submit: handleSubmit }) => (
|
{({ change: handleChange, data, submit: handleSubmit }) => (
|
||||||
<Layout>
|
<>
|
||||||
<Typography>
|
<Typography>
|
||||||
<FormattedMessage defaultMessage="Forgot your password? Don't worry, we'll reset it for you." />
|
<FormattedMessage defaultMessage="Forgot your password? Don't worry, we'll reset it for you." />
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@ -70,7 +69,7 @@ const ResetPasswordPage: React.FC<ResetPasswordPageProps> = props => {
|
||||||
description="password reset, button"
|
description="password reset, button"
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</Layout>
|
</>
|
||||||
)}
|
)}
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,7 +5,6 @@ import React from "react";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
import FormSpacer from "@saleor/components/FormSpacer";
|
import FormSpacer from "@saleor/components/FormSpacer";
|
||||||
import Layout from "../Layout";
|
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
const useStyles = makeStyles(
|
||||||
{
|
{
|
||||||
|
@ -33,7 +32,7 @@ const ResetPasswordSuccessPage: React.FC<
|
||||||
const classes = useStyles(props);
|
const classes = useStyles(props);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<>
|
||||||
<Typography>
|
<Typography>
|
||||||
<FormattedMessage defaultMessage="Success! If we have your e-mail, you’ll receive a message with instructions on how to reset your password." />
|
<FormattedMessage defaultMessage="Success! If we have your e-mail, you’ll receive a message with instructions on how to reset your password." />
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@ -47,7 +46,7 @@ const ResetPasswordSuccessPage: React.FC<
|
||||||
>
|
>
|
||||||
<FormattedMessage defaultMessage="Back to login" description="button" />
|
<FormattedMessage defaultMessage="Back to login" description="button" />
|
||||||
</Button>
|
</Button>
|
||||||
</Layout>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Route, Switch } from "react-router-dom";
|
import { Route, Switch } from "react-router-dom";
|
||||||
|
|
||||||
|
import Layout from "./components/Layout";
|
||||||
import { User } from "./types/User";
|
import { User } from "./types/User";
|
||||||
import {
|
import {
|
||||||
newPasswordPath,
|
newPasswordPath,
|
||||||
|
@ -12,10 +13,9 @@ import NewPassword from "./views/NewPassword";
|
||||||
import ResetPassword from "./views/ResetPassword";
|
import ResetPassword from "./views/ResetPassword";
|
||||||
import ResetPasswordSuccess from "./views/ResetPasswordSuccess";
|
import ResetPasswordSuccess from "./views/ResetPasswordSuccess";
|
||||||
|
|
||||||
const TOKEN_STORAGE_KEY = "dashboardAuth";
|
|
||||||
|
|
||||||
interface UserContext {
|
interface UserContext {
|
||||||
login: (username: string, password: string, persist: boolean) => void;
|
login: (username: string, password: string, persist: boolean) => void;
|
||||||
|
loginByToken: (token: string, user: User) => void;
|
||||||
logout: () => void;
|
logout: () => void;
|
||||||
tokenAuthLoading: boolean;
|
tokenAuthLoading: boolean;
|
||||||
tokenVerifyLoading: boolean;
|
tokenVerifyLoading: boolean;
|
||||||
|
@ -24,33 +24,24 @@ interface UserContext {
|
||||||
|
|
||||||
export const UserContext = React.createContext<UserContext>({
|
export const UserContext = React.createContext<UserContext>({
|
||||||
login: undefined,
|
login: undefined,
|
||||||
|
loginByToken: undefined,
|
||||||
logout: undefined,
|
logout: undefined,
|
||||||
tokenAuthLoading: false,
|
tokenAuthLoading: false,
|
||||||
tokenVerifyLoading: false
|
tokenVerifyLoading: false
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getAuthToken = () =>
|
|
||||||
localStorage.getItem(TOKEN_STORAGE_KEY) ||
|
|
||||||
sessionStorage.getItem(TOKEN_STORAGE_KEY);
|
|
||||||
|
|
||||||
export const setAuthToken = (token: string, persist: boolean) =>
|
|
||||||
persist
|
|
||||||
? localStorage.setItem(TOKEN_STORAGE_KEY, token)
|
|
||||||
: sessionStorage.setItem(TOKEN_STORAGE_KEY, token);
|
|
||||||
|
|
||||||
export const removeAuthToken = () => {
|
|
||||||
localStorage.removeItem(TOKEN_STORAGE_KEY);
|
|
||||||
sessionStorage.removeItem(TOKEN_STORAGE_KEY);
|
|
||||||
};
|
|
||||||
|
|
||||||
const AuthRouter: React.FC = () => (
|
const AuthRouter: React.FC = () => (
|
||||||
|
<Layout>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path={passwordResetPath} component={ResetPassword} />
|
|
||||||
<Route path={passwordResetSuccessPath} component={ResetPasswordSuccess} />
|
<Route path={passwordResetSuccessPath} component={ResetPasswordSuccess} />
|
||||||
|
<Route path={passwordResetPath} component={ResetPassword} />
|
||||||
<Route path={newPasswordPath} component={NewPassword} />
|
<Route path={newPasswordPath} component={NewPassword} />
|
||||||
<Route component={LoginView} />
|
<Route component={LoginView} />
|
||||||
</Switch>
|
</Switch>
|
||||||
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|
||||||
AuthRouter.displayName = "AuthRouter";
|
AuthRouter.displayName = "AuthRouter";
|
||||||
export default AuthRouter;
|
export default AuthRouter;
|
||||||
|
|
||||||
|
export * from "./utils";
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
RequestPasswordReset,
|
RequestPasswordReset,
|
||||||
RequestPasswordResetVariables
|
RequestPasswordResetVariables
|
||||||
} from "./types/RequestPasswordReset";
|
} from "./types/RequestPasswordReset";
|
||||||
|
import { SetPassword, SetPasswordVariables } from "./types/SetPassword";
|
||||||
import { TokenAuth, TokenAuthVariables } from "./types/TokenAuth";
|
import { TokenAuth, TokenAuthVariables } from "./types/TokenAuth";
|
||||||
import { VerifyToken, VerifyTokenVariables } from "./types/VerifyToken";
|
import { VerifyToken, VerifyTokenVariables } from "./types/VerifyToken";
|
||||||
|
|
||||||
|
@ -78,3 +79,23 @@ export const RequestPasswordResetMutation = TypedMutation<
|
||||||
RequestPasswordReset,
|
RequestPasswordReset,
|
||||||
RequestPasswordResetVariables
|
RequestPasswordResetVariables
|
||||||
>(requestPasswordReset);
|
>(requestPasswordReset);
|
||||||
|
|
||||||
|
export const setPassword = gql`
|
||||||
|
${fragmentUser}
|
||||||
|
mutation SetPassword($email: String!, $password: String!, $token: String!) {
|
||||||
|
setPassword(email: $email, password: $password, token: $token) {
|
||||||
|
token
|
||||||
|
errors {
|
||||||
|
field
|
||||||
|
message
|
||||||
|
}
|
||||||
|
user {
|
||||||
|
...User
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export const SetPasswordMutation = TypedMutation<
|
||||||
|
SetPassword,
|
||||||
|
SetPasswordVariables
|
||||||
|
>(setPassword);
|
||||||
|
|
55
src/auth/types/SetPassword.ts
Normal file
55
src/auth/types/SetPassword.ts
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
import { PermissionEnum } from "./../../types/globalTypes";
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
|
// GraphQL mutation operation: SetPassword
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
export interface SetPassword_setPassword_errors {
|
||||||
|
__typename: "Error";
|
||||||
|
field: string | null;
|
||||||
|
message: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetPassword_setPassword_user_permissions {
|
||||||
|
__typename: "PermissionDisplay";
|
||||||
|
code: PermissionEnum;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetPassword_setPassword_user_avatar {
|
||||||
|
__typename: "Image";
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetPassword_setPassword_user {
|
||||||
|
__typename: "User";
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
isStaff: boolean;
|
||||||
|
note: string | null;
|
||||||
|
permissions: (SetPassword_setPassword_user_permissions | null)[] | null;
|
||||||
|
avatar: SetPassword_setPassword_user_avatar | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetPassword_setPassword {
|
||||||
|
__typename: "SetPassword";
|
||||||
|
token: string | null;
|
||||||
|
errors: (SetPassword_setPassword_errors | null)[];
|
||||||
|
user: SetPassword_setPassword_user | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetPassword {
|
||||||
|
setPassword: SetPassword_setPassword | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetPasswordVariables {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
token: string;
|
||||||
|
}
|
|
@ -1,8 +1,5 @@
|
||||||
import { stringify as stringifyQs } from "qs";
|
import { stringify as stringifyQs } from "qs";
|
||||||
|
|
||||||
export const loginPath = "/login/";
|
|
||||||
export const loginUrl = loginPath;
|
|
||||||
|
|
||||||
export const passwordResetPath = "/reset-password/";
|
export const passwordResetPath = "/reset-password/";
|
||||||
export const passwordResetUrl = passwordResetPath;
|
export const passwordResetUrl = passwordResetPath;
|
||||||
|
|
||||||
|
@ -11,6 +8,7 @@ export const passwordResetSuccessUrl = passwordResetSuccessPath;
|
||||||
|
|
||||||
export const newPasswordPath = "/new-password/";
|
export const newPasswordPath = "/new-password/";
|
||||||
export interface NewPasswordUrlQueryParams {
|
export interface NewPasswordUrlQueryParams {
|
||||||
|
email: string;
|
||||||
token: string;
|
token: string;
|
||||||
}
|
}
|
||||||
export const newPasswordUrl = (params?: NewPasswordUrlQueryParams) =>
|
export const newPasswordUrl = (params?: NewPasswordUrlQueryParams) =>
|
||||||
|
|
15
src/auth/utils.ts
Normal file
15
src/auth/utils.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
const TOKEN_STORAGE_KEY = "dashboardAuth";
|
||||||
|
|
||||||
|
export const getAuthToken = () =>
|
||||||
|
localStorage.getItem(TOKEN_STORAGE_KEY) ||
|
||||||
|
sessionStorage.getItem(TOKEN_STORAGE_KEY);
|
||||||
|
|
||||||
|
export const setAuthToken = (token: string, persist: boolean) =>
|
||||||
|
persist
|
||||||
|
? localStorage.setItem(TOKEN_STORAGE_KEY, token)
|
||||||
|
: sessionStorage.setItem(TOKEN_STORAGE_KEY, token);
|
||||||
|
|
||||||
|
export const removeAuthToken = () => {
|
||||||
|
localStorage.removeItem(TOKEN_STORAGE_KEY);
|
||||||
|
sessionStorage.removeItem(TOKEN_STORAGE_KEY);
|
||||||
|
};
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { parse as parseQs } from "qs";
|
||||||
|
import React from "react";
|
||||||
|
import { RouteComponentProps } from "react-router";
|
||||||
|
|
||||||
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
|
import useUser from "@saleor/hooks/useUser";
|
||||||
|
import NewPasswordPage, {
|
||||||
|
NewPasswordPageFormData
|
||||||
|
} from "../components/NewPasswordPage";
|
||||||
|
import { SetPasswordMutation } from "../mutations";
|
||||||
|
import { SetPassword } from "../types/SetPassword";
|
||||||
|
import { NewPasswordUrlQueryParams } from "../urls";
|
||||||
|
|
||||||
|
const NewPassword: React.FC<RouteComponentProps> = ({ location }) => {
|
||||||
|
const navigate = useNavigator();
|
||||||
|
const { loginByToken } = useUser();
|
||||||
|
|
||||||
|
const params: NewPasswordUrlQueryParams = parseQs(location.search.substr(1));
|
||||||
|
|
||||||
|
const handleSetPassword = async (data: SetPassword) => {
|
||||||
|
if (data.setPassword.errors.length === 0) {
|
||||||
|
loginByToken(data.setPassword.token, data.setPassword.user);
|
||||||
|
navigate("/", true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SetPasswordMutation onCompleted={handleSetPassword}>
|
||||||
|
{(setPassword, setPasswordOpts) => {
|
||||||
|
const handleSubmit = (data: NewPasswordPageFormData) =>
|
||||||
|
setPassword({
|
||||||
|
variables: {
|
||||||
|
email: params.email,
|
||||||
|
password: data.password,
|
||||||
|
token: params.token
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NewPasswordPage
|
||||||
|
disabled={setPasswordOpts.loading}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</SetPasswordMutation>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
NewPassword.displayName = "NewPassword";
|
||||||
|
export default NewPassword;
|
|
@ -1,8 +1,8 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import urlJoin from "url-join";
|
import urlJoin from "url-join";
|
||||||
|
|
||||||
|
import { APP_MOUNT_URI } from "@saleor/config";
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useShop from "@saleor/hooks/useShop";
|
|
||||||
import ResetPasswordPage, {
|
import ResetPasswordPage, {
|
||||||
ResetPasswordPageFormData
|
ResetPasswordPageFormData
|
||||||
} from "../components/ResetPasswordPage";
|
} from "../components/ResetPasswordPage";
|
||||||
|
@ -12,7 +12,6 @@ import { newPasswordUrl, passwordResetSuccessUrl } from "../urls";
|
||||||
|
|
||||||
const ResetPasswordView: React.FC = () => {
|
const ResetPasswordView: React.FC = () => {
|
||||||
const navigate = useNavigator();
|
const navigate = useNavigator();
|
||||||
const shop = useShop();
|
|
||||||
|
|
||||||
const handleRequestPasswordReset = (data: RequestPasswordReset) => {
|
const handleRequestPasswordReset = (data: RequestPasswordReset) => {
|
||||||
if (data.requestPasswordReset.errors.length === 0) {
|
if (data.requestPasswordReset.errors.length === 0) {
|
||||||
|
@ -27,7 +26,11 @@ const ResetPasswordView: React.FC = () => {
|
||||||
requestPasswordReset({
|
requestPasswordReset({
|
||||||
variables: {
|
variables: {
|
||||||
email: data.email,
|
email: data.email,
|
||||||
redirectUrl: urlJoin(shop.domain.url, newPasswordUrl())
|
redirectUrl: urlJoin(
|
||||||
|
window.location.origin,
|
||||||
|
APP_MOUNT_URI,
|
||||||
|
newPasswordUrl().replace(/\?/, "")
|
||||||
|
)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,11 @@ import React from "react";
|
||||||
|
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import ResetPasswordSuccessPage from "../components/ResetPasswordSuccessPage";
|
import ResetPasswordSuccessPage from "../components/ResetPasswordSuccessPage";
|
||||||
import { loginUrl } from "../urls";
|
|
||||||
|
|
||||||
const ResetPasswordSuccessView: React.FC = () => {
|
const ResetPasswordSuccessView: React.FC = () => {
|
||||||
const navigate = useNavigator();
|
const navigate = useNavigator();
|
||||||
|
|
||||||
return <ResetPasswordSuccessPage onBack={() => navigate(loginUrl)} />;
|
return <ResetPasswordSuccessPage onBack={() => navigate("/")} />;
|
||||||
};
|
};
|
||||||
ResetPasswordSuccessView.displayName = "ResetPasswordSuccessView";
|
ResetPasswordSuccessView.displayName = "ResetPasswordSuccessView";
|
||||||
export default ResetPasswordSuccessView;
|
export default ResetPasswordSuccessView;
|
||||||
|
|
|
@ -63,6 +63,7 @@ const invalidTokenLink = onError((error: ResponseError) => {
|
||||||
|
|
||||||
const authLink = setContext((_, context) => {
|
const authLink = setContext((_, context) => {
|
||||||
const authToken = getAuthToken();
|
const authToken = getAuthToken();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...context,
|
...context,
|
||||||
headers: {
|
headers: {
|
||||||
|
|
Loading…
Reference in a new issue