wip
This commit is contained in:
parent
b582a7f51a
commit
48ca3b9e15
18 changed files with 225 additions and 50 deletions
|
@ -9,15 +9,13 @@ import { User } from "./types/User";
|
|||
import { VerifyToken, VerifyTokenVariables } from "./types/VerifyToken";
|
||||
|
||||
interface AuthProviderOperationsProps {
|
||||
children: (
|
||||
props: {
|
||||
children: (props: {
|
||||
hasToken: boolean;
|
||||
isAuthenticated: boolean;
|
||||
tokenAuthLoading: boolean;
|
||||
tokenVerifyLoading: boolean;
|
||||
user: User;
|
||||
}
|
||||
) => React.ReactNode;
|
||||
}) => React.ReactNode;
|
||||
}
|
||||
const AuthProviderOperations: React.StatelessComponent<
|
||||
AuthProviderOperationsProps
|
||||
|
@ -41,15 +39,13 @@ const AuthProviderOperations: React.StatelessComponent<
|
|||
};
|
||||
|
||||
interface AuthProviderProps {
|
||||
children: (
|
||||
props: {
|
||||
children: (props: {
|
||||
hasToken: boolean;
|
||||
isAuthenticated: boolean;
|
||||
tokenAuthLoading: boolean;
|
||||
tokenVerifyLoading: boolean;
|
||||
user: User;
|
||||
}
|
||||
) => React.ReactNode;
|
||||
}) => 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(),
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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} />
|
||||
));
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./NewPasswordPage";
|
||||
export * from "./NewPasswordPage";
|
|
@ -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} />
|
||||
));
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./ResetPasswordPage";
|
||||
export * from "./ResetPasswordPage";
|
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./ResetPasswordSuccessPage";
|
||||
export * from "./ResetPasswordSuccessPage";
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
27
src/auth/types/RequestPasswordReset.ts
Normal file
27
src/auth/types/RequestPasswordReset.ts
Normal 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
17
src/auth/urls.ts
Normal 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);
|
|
@ -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}
|
||||
disableLoginButton={tokenAuthLoading}
|
||||
onPasswordRecovery={() => navigate(passwordResetUrl)}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</UserContext.Consumer>
|
||||
);
|
||||
};
|
||||
LoginView.displayName = "LoginView";
|
||||
export default LoginView;
|
||||
|
|
0
src/auth/views/NewPassword.tsx
Normal file
0
src/auth/views/NewPassword.tsx
Normal file
45
src/auth/views/ResetPassword.tsx
Normal file
45
src/auth/views/ResetPassword.tsx
Normal 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;
|
13
src/auth/views/ResetPasswordSuccess.tsx
Normal file
13
src/auth/views/ResetPasswordSuccess.tsx
Normal 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;
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue