From 7290087680fadf04d0515e45f68b2bbe3516ad34 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 2 Sep 2019 15:20:36 +0200 Subject: [PATCH 01/15] Add dynamic stories --- babel.config.js | 3 ++- package-lock.json | 50 +++++++++++++++++------------------------ package.json | 1 + src/storybook/config.js | 8 ++++++- 4 files changed, 30 insertions(+), 32 deletions(-) diff --git a/babel.config.js b/babel.config.js index 723c15517..0db43fbd4 100644 --- a/babel.config.js +++ b/babel.config.js @@ -37,7 +37,8 @@ module.exports = api => { extractFromFormatMessageCall: true, messagesDir: "build/locale/" } - ] + ], + "macros" ]; return { diff --git a/package-lock.json b/package-lock.json index cdfa829e5..eedf6a666 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9409,8 +9409,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -9428,13 +9427,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -9447,18 +9444,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -9561,8 +9555,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -9572,7 +9565,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -9585,20 +9577,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.3.5", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -9615,7 +9604,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -9688,8 +9676,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -9699,7 +9686,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -9775,8 +9761,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -9806,7 +9791,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -9824,7 +9808,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -9863,13 +9846,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.0.3", - "bundled": true, - "optional": true + "bundled": true } } }, @@ -16962,6 +16943,15 @@ "tough-cookie": "^2.3.3" } }, + "require-context.macro": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/require-context.macro/-/require-context.macro-1.1.1.tgz", + "integrity": "sha512-l1XH5HruDyG+Iwo5pz39EGbOFVtoYQt8cl7mJ6KJFWzARNJnpb+XUui+jBQUGlJ8SJOdx+QDh784e8b42PxLXA==", + "dev": true, + "requires": { + "@types/webpack-env": "^1.14.0" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", diff --git a/package.json b/package.json index 6c8830060..d18a61efb 100644 --- a/package.json +++ b/package.json @@ -120,6 +120,7 @@ "react-intl-po": "^2.2.2", "react-test-renderer": "^16.8.6", "regenerator-runtime": "^0.11.1", + "require-context.macro": "^1.1.1", "rimraf": "^2.7.0", "testcafe": "^1.3.3", "ts-jest": "^23.10.5", diff --git a/src/storybook/config.js b/src/storybook/config.js index 78aff494a..89b3a57cf 100644 --- a/src/storybook/config.js +++ b/src/storybook/config.js @@ -1,7 +1,13 @@ /* eslint-disable */ -const { configure } = require("@storybook/react"); +import requireContext from "require-context.macro"; +import { configure } from "@storybook/react"; + +const req = requireContext("../", true, /.stories.tsx$/); function loadStories() { + // Story autodiscovery + req.keys().forEach(filename => req(filename)); + // Components require("./stories/components/ActionDialog"); require("./stories/components/AddressEdit"); From 70b85242392638cdc217e070745866be1538f3b0 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 2 Sep 2019 15:52:43 +0200 Subject: [PATCH 02/15] Add reset password page --- src/auth/components/Layout.tsx | 100 +++++++++ src/auth/components/LoginPage/LoginPage.tsx | 200 ++++++------------ .../ResetPasswordPage.stories.tsx | 9 + .../ResetPasswordPage/ResetPasswordPage.tsx | 78 +++++++ .../components/ResetPasswordPage/index.ts | 0 5 files changed, 251 insertions(+), 136 deletions(-) create mode 100644 src/auth/components/Layout.tsx create mode 100644 src/auth/components/ResetPasswordPage/ResetPasswordPage.stories.tsx create mode 100644 src/auth/components/ResetPasswordPage/ResetPasswordPage.tsx create mode 100644 src/auth/components/ResetPasswordPage/index.ts diff --git a/src/auth/components/Layout.tsx b/src/auth/components/Layout.tsx new file mode 100644 index 000000000..3d217293b --- /dev/null +++ b/src/auth/components/Layout.tsx @@ -0,0 +1,100 @@ +import { Theme } from "@material-ui/core/styles"; +import { makeStyles } from "@material-ui/styles"; +import React from "react"; +import SVG from "react-inlinesvg"; + +import backgroundArt from "@assets/images/login-background.svg"; +import saleorDarkLogo from "@assets/images/logo-dark.svg"; +import saleorLightLogo from "@assets/images/logo-light.svg"; +import useTheme from "@saleor/hooks/useTheme"; + +const useStyles = makeStyles( + (theme: Theme) => ({ + logo: { + "& svg": { + display: "block", + height: 40, + marginBottom: theme.spacing.unit * 4 + } + }, + mainPanel: { + [theme.breakpoints.down("sm")]: { + padding: theme.spacing.unit * 2 + }, + background: theme.palette.background.paper, + display: "flex", + flexDirection: "column", + height: "100vh", + justifyContent: "center", + padding: theme.spacing.unit * 6, + width: "100%" + }, + mainPanelContent: { + [theme.breakpoints.up("xs")]: { + width: "100%" + }, + [theme.breakpoints.up("sm")]: { + width: 328 + }, + "@media (min-width: 1440px)": { + width: 464 + }, + margin: "auto", + width: "100%" + }, + root: { + [theme.breakpoints.up("lg")]: { + gridTemplateColumns: "376px 1fr" + }, + "@media (min-width: 1440px)": { + gridTemplateColumns: "520px 1fr" + }, + display: "grid", + gridTemplateColumns: "1fr", + height: "100vh", + overflow: "hidden", + width: "100vw" + }, + sidebar: { + [theme.breakpoints.up("lg")]: { + display: "block" + }, + display: "none" + }, + sidebarArt: { + "& svg": { + width: "100%" + } + } + }), + { + name: "Layout" + } +); + +const Layout: React.FC = props => { + const { children } = props; + + const classes = useStyles(props); + const { isDark } = useTheme(); + + return ( +
+
+ +
+
+
+ + {children} +
+
+
+ ); +}; + +Layout.displayName = "Layout"; +export default Layout; diff --git a/src/auth/components/LoginPage/LoginPage.tsx b/src/auth/components/LoginPage/LoginPage.tsx index c361257d1..76c801638 100644 --- a/src/auth/components/LoginPage/LoginPage.tsx +++ b/src/auth/components/LoginPage/LoginPage.tsx @@ -8,16 +8,12 @@ import { import TextField from "@material-ui/core/TextField"; import Typography from "@material-ui/core/Typography"; import React from "react"; -import SVG from "react-inlinesvg"; import { FormattedMessage, useIntl } from "react-intl"; +import Layout from "../Layout"; -import backgroundArt from "@assets/images/login-background.svg"; -import saleorDarkLogo from "@assets/images/logo-dark.svg"; -import saleorLightLogo from "@assets/images/logo-light.svg"; import { ControlledCheckbox } from "@saleor/components/ControlledCheckbox"; import Form from "@saleor/components/Form"; import { FormSpacer } from "@saleor/components/FormSpacer"; -import useTheme from "@saleor/hooks/useTheme"; import { commonMessages } from "@saleor/intl"; export interface FormData { @@ -41,38 +37,6 @@ const styles = (theme: Theme) => loginButton: { width: 140 }, - logo: { - "& svg": { - display: "block", - height: 40, - marginBottom: theme.spacing.unit * 4 - } - }, - mainPanel: { - [theme.breakpoints.down("sm")]: { - padding: theme.spacing.unit * 2 - }, - background: theme.palette.background.paper, - display: "flex", - flexDirection: "column", - height: "100vh", - justifyContent: "center", - padding: theme.spacing.unit * 6, - width: "100%" - }, - mainPanelContent: { - [theme.breakpoints.up("xs")]: { - width: "100%" - }, - [theme.breakpoints.up("sm")]: { - width: 328 - }, - "@media (min-width: 1440px)": { - width: 464 - }, - margin: "auto", - width: "100%" - }, panel: { "& span": { color: theme.palette.error.contrastText @@ -81,30 +45,6 @@ const styles = (theme: Theme) => borderRadius: theme.spacing.unit, marginBottom: theme.spacing.unit * 3, padding: theme.spacing.unit * 1.5 - }, - root: { - [theme.breakpoints.up("lg")]: { - gridTemplateColumns: "376px 1fr" - }, - "@media (min-width: 1440px)": { - gridTemplateColumns: "520px 1fr" - }, - display: "grid", - gridTemplateColumns: "1fr", - height: "100vh", - overflow: "hidden", - width: "100vw" - }, - sidebar: { - [theme.breakpoints.up("lg")]: { - display: "block" - }, - display: "none" - }, - sidebarArt: { - "& svg": { - width: "100%" - } } }); @@ -117,7 +57,6 @@ export interface LoginCardProps extends WithStyles { const LoginCard = withStyles(styles, { name: "LoginCard" })( ({ classes, error, disableLoginButton, onSubmit }: LoginCardProps) => { - const { isDark } = useTheme(); const intl = useIntl(); return ( @@ -126,82 +65,71 @@ const LoginCard = withStyles(styles, { name: "LoginCard" })( onSubmit={onSubmit} > {({ change: handleChange, data, submit: handleSubmit }) => ( -
-
- -
-
-
- - {error && ( -
- - - -
- )} - - - - -
- - - -
- {/* - - {i18n.t("Reset your password")} - */} + + {error && ( +
+ + +
+ )} + + + + +
+ + +
-
+ + + + + )} ); diff --git a/src/auth/components/ResetPasswordPage/ResetPasswordPage.stories.tsx b/src/auth/components/ResetPasswordPage/ResetPasswordPage.stories.tsx new file mode 100644 index 000000000..ef17d5b28 --- /dev/null +++ b/src/auth/components/ResetPasswordPage/ResetPasswordPage.stories.tsx @@ -0,0 +1,9 @@ +import { storiesOf } from "@storybook/react"; +import React from "react"; + +import Decorator from "@saleor/storybook//Decorator"; +import ResetPasswordPage from "./ResetPasswordPage"; + +storiesOf("Views / Authentication / Reset password", module) + .addDecorator(Decorator) + .add("default", () => undefined} />); diff --git a/src/auth/components/ResetPasswordPage/ResetPasswordPage.tsx b/src/auth/components/ResetPasswordPage/ResetPasswordPage.tsx new file mode 100644 index 000000000..2bf0e43e6 --- /dev/null +++ b/src/auth/components/ResetPasswordPage/ResetPasswordPage.tsx @@ -0,0 +1,78 @@ +import Button from "@material-ui/core/Button"; +import { Theme } from "@material-ui/core/styles"; +import TextField from "@material-ui/core/TextField"; +import Typography from "@material-ui/core/Typography"; +import { makeStyles } from "@material-ui/styles"; +import React from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import Form from "@saleor/components/Form"; +import FormSpacer from "@saleor/components/FormSpacer"; +import { commonMessages } from "@saleor/intl"; +import Layout from "../Layout"; + +const useStyles = makeStyles( + { + submit: { + width: "100%" + } + }, + { + name: "ResetPasswordPage" + } +); + +export interface ResetPasswordPageFormData { + email: string; +} +export interface ResetPasswordPageProps { + onSubmit: (data: ResetPasswordPageFormData) => void; +} + +const ResetPasswordPage: React.FC = props => { + const { onSubmit } = props; + + const classes = useStyles(props); + const intl = useIntl(); + + return ( +
+ {({ change: handleChange, data, submit: handleSubmit }) => ( + + + + + + + + + + )} +
+ ); +}; + +ResetPasswordPage.displayName = "ResetPasswordPage"; +export default ResetPasswordPage; diff --git a/src/auth/components/ResetPasswordPage/index.ts b/src/auth/components/ResetPasswordPage/index.ts new file mode 100644 index 000000000..e69de29bb From b582a7f51ac815314f1e615e672644fef804b74e Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 2 Sep 2019 17:56:59 +0200 Subject: [PATCH 03/15] Add password reset flow components --- .../NewPasswordPage.stories.tsx | 9 ++ .../NewPasswordPage/NewPasswordPage.tsx | 114 ++++++++++++++++++ src/auth/components/NewPasswordPage/index.ts | 0 .../ResetPasswordPage/ResetPasswordPage.tsx | 1 - .../ResetPasswordSuccessPage.stories.tsx | 11 ++ .../ResetPasswordSuccessPage.tsx | 55 +++++++++ .../ResetPasswordSuccessPage/index.ts | 0 7 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 src/auth/components/NewPasswordPage/NewPasswordPage.stories.tsx create mode 100644 src/auth/components/NewPasswordPage/NewPasswordPage.tsx create mode 100644 src/auth/components/NewPasswordPage/index.ts create mode 100644 src/auth/components/ResetPasswordSuccessPage/ResetPasswordSuccessPage.stories.tsx create mode 100644 src/auth/components/ResetPasswordSuccessPage/ResetPasswordSuccessPage.tsx create mode 100644 src/auth/components/ResetPasswordSuccessPage/index.ts diff --git a/src/auth/components/NewPasswordPage/NewPasswordPage.stories.tsx b/src/auth/components/NewPasswordPage/NewPasswordPage.stories.tsx new file mode 100644 index 000000000..63de88bda --- /dev/null +++ b/src/auth/components/NewPasswordPage/NewPasswordPage.stories.tsx @@ -0,0 +1,9 @@ +import { storiesOf } from "@storybook/react"; +import React from "react"; + +import Decorator from "@saleor/storybook//Decorator"; +import NewPasswordPage from "./NewPasswordPage"; + +storiesOf("Views / Authentication / Set up a new password", module) + .addDecorator(Decorator) + .add("default", () => undefined} />); diff --git a/src/auth/components/NewPasswordPage/NewPasswordPage.tsx b/src/auth/components/NewPasswordPage/NewPasswordPage.tsx new file mode 100644 index 000000000..5019637d8 --- /dev/null +++ b/src/auth/components/NewPasswordPage/NewPasswordPage.tsx @@ -0,0 +1,114 @@ +import Button from "@material-ui/core/Button"; +import TextField from "@material-ui/core/TextField"; +import Typography from "@material-ui/core/Typography"; +import { makeStyles } from "@material-ui/styles"; +import React from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import Form from "@saleor/components/Form"; +import FormSpacer from "@saleor/components/FormSpacer"; +import Layout from "../Layout"; + +const useStyles = makeStyles( + { + submit: { + width: "100%" + } + }, + { + name: "NewPasswordPage" + } +); + +export interface NewPasswordPageFormData { + password: string; + confirmPassword: string; +} +export interface NewPasswordPageProps { + onSubmit: (data: NewPasswordPageFormData) => void; +} + +const initialForm: NewPasswordPageFormData = { + confirmPassword: "", + password: "" +}; + +const NewPasswordPage: React.FC = props => { + const { onSubmit } = props; + + const classes = useStyles(props); + const intl = useIntl(); + + return ( +
+ {({ change: handleChange, data, submit: handleSubmit }) => { + const passwordError = + data.password !== data.confirmPassword && data.password.length > 0; + + return ( + + + + + + + + + + + + ); + }} +
+ ); +}; + +NewPasswordPage.displayName = "NewPasswordPage"; +export default NewPasswordPage; diff --git a/src/auth/components/NewPasswordPage/index.ts b/src/auth/components/NewPasswordPage/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/auth/components/ResetPasswordPage/ResetPasswordPage.tsx b/src/auth/components/ResetPasswordPage/ResetPasswordPage.tsx index 2bf0e43e6..90c3ff061 100644 --- a/src/auth/components/ResetPasswordPage/ResetPasswordPage.tsx +++ b/src/auth/components/ResetPasswordPage/ResetPasswordPage.tsx @@ -1,5 +1,4 @@ import Button from "@material-ui/core/Button"; -import { Theme } from "@material-ui/core/styles"; import TextField from "@material-ui/core/TextField"; import Typography from "@material-ui/core/Typography"; import { makeStyles } from "@material-ui/styles"; diff --git a/src/auth/components/ResetPasswordSuccessPage/ResetPasswordSuccessPage.stories.tsx b/src/auth/components/ResetPasswordSuccessPage/ResetPasswordSuccessPage.stories.tsx new file mode 100644 index 000000000..4edb5455b --- /dev/null +++ b/src/auth/components/ResetPasswordSuccessPage/ResetPasswordSuccessPage.stories.tsx @@ -0,0 +1,11 @@ +import { storiesOf } from "@storybook/react"; +import React from "react"; + +import Decorator from "@saleor/storybook/Decorator"; +import ResetPasswordSuccessPage from "./ResetPasswordSuccessPage"; + +storiesOf("Views / Authentication / Reset password success", module) + .addDecorator(Decorator) + .add("default", () => ( + undefined} /> + )); diff --git a/src/auth/components/ResetPasswordSuccessPage/ResetPasswordSuccessPage.tsx b/src/auth/components/ResetPasswordSuccessPage/ResetPasswordSuccessPage.tsx new file mode 100644 index 000000000..b7b1bb694 --- /dev/null +++ b/src/auth/components/ResetPasswordSuccessPage/ResetPasswordSuccessPage.tsx @@ -0,0 +1,55 @@ +import Button from "@material-ui/core/Button"; +import Typography from "@material-ui/core/Typography"; +import { makeStyles } from "@material-ui/styles"; +import React from "react"; +import { FormattedMessage } from "react-intl"; + +import FormSpacer from "@saleor/components/FormSpacer"; +import Layout from "../Layout"; + +const useStyles = makeStyles( + { + submit: { + width: "100%" + } + }, + { + name: "ResetPasswordSuccessPage" + } +); + +export interface ResetPasswordSuccessPageFormData { + email: string; +} +export interface ResetPasswordSuccessPageProps { + onBack: () => void; +} + +const ResetPasswordSuccessPage: React.FC< + ResetPasswordSuccessPageProps +> = props => { + const { onBack } = props; + + const classes = useStyles(props); + + return ( + + + + + + + + ); +}; + +ResetPasswordSuccessPage.displayName = "ResetPasswordSuccessPage"; +export default ResetPasswordSuccessPage; diff --git a/src/auth/components/ResetPasswordSuccessPage/index.ts b/src/auth/components/ResetPasswordSuccessPage/index.ts new file mode 100644 index 000000000..e69de29bb From 48ca3b9e15882076b8954938baea51cce3914569 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 2 Sep 2019 21:23:37 +0200 Subject: [PATCH 04/15] wip --- src/auth/AuthProvider.tsx | 41 +++++++++-------- src/auth/components/LoginPage/LoginPage.tsx | 10 ++++- .../NewPasswordPage.stories.tsx | 7 ++- .../NewPasswordPage/NewPasswordPage.tsx | 7 ++- src/auth/components/NewPasswordPage/index.ts | 2 + .../ResetPasswordPage.stories.tsx | 7 ++- .../ResetPasswordPage/ResetPasswordPage.tsx | 5 ++- .../components/ResetPasswordPage/index.ts | 2 + .../ResetPasswordSuccessPage/index.ts | 2 + src/auth/index.tsx | 28 +++++++++++- src/auth/mutations.ts | 19 ++++++++ src/auth/types/RequestPasswordReset.ts | 27 +++++++++++ src/auth/urls.ts | 17 +++++++ src/auth/views/Login.tsx | 38 ++++++++-------- src/auth/views/NewPassword.tsx | 0 src/auth/views/ResetPassword.tsx | 45 +++++++++++++++++++ src/auth/views/ResetPasswordSuccess.tsx | 13 ++++++ src/index.tsx | 5 +-- 18 files changed, 225 insertions(+), 50 deletions(-) create mode 100644 src/auth/types/RequestPasswordReset.ts create mode 100644 src/auth/urls.ts create mode 100644 src/auth/views/NewPassword.tsx create mode 100644 src/auth/views/ResetPassword.tsx create mode 100644 src/auth/views/ResetPasswordSuccess.tsx diff --git a/src/auth/AuthProvider.tsx b/src/auth/AuthProvider.tsx index 147dec6b0..5fa3a5d22 100644 --- a/src/auth/AuthProvider.tsx +++ b/src/auth/AuthProvider.tsx @@ -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; tokenVerify: PartialMutationProviderOutput; } @@ -116,9 +112,16 @@ class AuthProvider extends React.Component< const { children, tokenAuth, tokenVerify } = this.props; const { user } = this.state; const isAuthenticated = !!user; + return ( {children({ hasToken: !!getAuthToken(), diff --git a/src/auth/components/LoginPage/LoginPage.tsx b/src/auth/components/LoginPage/LoginPage.tsx index 76c801638..c94b72433 100644 --- a/src/auth/components/LoginPage/LoginPage.tsx +++ b/src/auth/components/LoginPage/LoginPage.tsx @@ -56,7 +56,13 @@ export interface LoginCardProps extends WithStyles { } 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" })(
- + undefined} />); + .add("default", () => ( + undefined} /> + )) + .add("loading", () => ( + undefined} /> + )); diff --git a/src/auth/components/NewPasswordPage/NewPasswordPage.tsx b/src/auth/components/NewPasswordPage/NewPasswordPage.tsx index 5019637d8..2a75498f1 100644 --- a/src/auth/components/NewPasswordPage/NewPasswordPage.tsx +++ b/src/auth/components/NewPasswordPage/NewPasswordPage.tsx @@ -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 = props => { - const { onSubmit } = props; + const { disabled, onSubmit } = props; const classes = useStyles(props); const intl = useIntl(); @@ -55,6 +56,7 @@ const NewPasswordPage: React.FC = props => { autoFocus fullWidth autoComplete="none" + disabled={disabled} label={intl.formatMessage({ defaultMessage: "New Password" })} @@ -72,6 +74,7 @@ const NewPasswordPage: React.FC = props => { fullWidth error={passwordError} autoComplete="none" + disabled={disabled} label={intl.formatMessage({ defaultMessage: "Confirm Password" })} @@ -93,7 +96,7 @@ const NewPasswordPage: React.FC = props => { - + ); }} diff --git a/src/auth/components/ResetPasswordPage/ResetPasswordPage.tsx b/src/auth/components/ResetPasswordPage/ResetPasswordPage.tsx index 64a0c9ec4..eddb94c11 100644 --- a/src/auth/components/ResetPasswordPage/ResetPasswordPage.tsx +++ b/src/auth/components/ResetPasswordPage/ResetPasswordPage.tsx @@ -8,7 +8,6 @@ import { FormattedMessage, useIntl } from "react-intl"; import Form from "@saleor/components/Form"; import FormSpacer from "@saleor/components/FormSpacer"; import { commonMessages } from "@saleor/intl"; -import Layout from "../Layout"; const useStyles = makeStyles( { @@ -38,7 +37,7 @@ const ResetPasswordPage: React.FC = props => { return (
{({ change: handleChange, data, submit: handleSubmit }) => ( - + <> @@ -70,7 +69,7 @@ const ResetPasswordPage: React.FC = props => { description="password reset, button" /> - + )}
); diff --git a/src/auth/components/ResetPasswordSuccessPage/ResetPasswordSuccessPage.tsx b/src/auth/components/ResetPasswordSuccessPage/ResetPasswordSuccessPage.tsx index b7b1bb694..ec3ef1fc2 100644 --- a/src/auth/components/ResetPasswordSuccessPage/ResetPasswordSuccessPage.tsx +++ b/src/auth/components/ResetPasswordSuccessPage/ResetPasswordSuccessPage.tsx @@ -5,7 +5,6 @@ import React from "react"; import { FormattedMessage } from "react-intl"; import FormSpacer from "@saleor/components/FormSpacer"; -import Layout from "../Layout"; const useStyles = makeStyles( { @@ -33,7 +32,7 @@ const ResetPasswordSuccessPage: React.FC< const classes = useStyles(props); return ( - + <> @@ -47,7 +46,7 @@ const ResetPasswordSuccessPage: React.FC< > - + ); }; diff --git a/src/auth/index.tsx b/src/auth/index.tsx index 80021af2f..74fb61b65 100644 --- a/src/auth/index.tsx +++ b/src/auth/index.tsx @@ -1,6 +1,7 @@ import React from "react"; import { Route, Switch } from "react-router-dom"; +import Layout from "./components/Layout"; import { User } from "./types/User"; import { newPasswordPath, @@ -12,10 +13,9 @@ 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; + loginByToken: (token: string, user: User) => void; logout: () => void; tokenAuthLoading: boolean; tokenVerifyLoading: boolean; @@ -24,33 +24,24 @@ interface UserContext { export const UserContext = React.createContext({ login: undefined, + loginByToken: undefined, logout: undefined, tokenAuthLoading: 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 = () => ( - - - - - - + + + + + + + + ); AuthRouter.displayName = "AuthRouter"; export default AuthRouter; + +export * from "./utils"; diff --git a/src/auth/mutations.ts b/src/auth/mutations.ts index 3f6509be7..6791e4b0d 100644 --- a/src/auth/mutations.ts +++ b/src/auth/mutations.ts @@ -5,6 +5,7 @@ import { RequestPasswordReset, RequestPasswordResetVariables } from "./types/RequestPasswordReset"; +import { SetPassword, SetPasswordVariables } from "./types/SetPassword"; import { TokenAuth, TokenAuthVariables } from "./types/TokenAuth"; import { VerifyToken, VerifyTokenVariables } from "./types/VerifyToken"; @@ -78,3 +79,23 @@ export const RequestPasswordResetMutation = TypedMutation< RequestPasswordReset, RequestPasswordResetVariables >(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); diff --git a/src/auth/types/SetPassword.ts b/src/auth/types/SetPassword.ts new file mode 100644 index 000000000..708560696 --- /dev/null +++ b/src/auth/types/SetPassword.ts @@ -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; +} diff --git a/src/auth/urls.ts b/src/auth/urls.ts index 62556a0e9..c0ac7c68b 100644 --- a/src/auth/urls.ts +++ b/src/auth/urls.ts @@ -1,8 +1,5 @@ import { stringify as stringifyQs } from "qs"; -export const loginPath = "/login/"; -export const loginUrl = loginPath; - export const passwordResetPath = "/reset-password/"; export const passwordResetUrl = passwordResetPath; @@ -11,6 +8,7 @@ export const passwordResetSuccessUrl = passwordResetSuccessPath; export const newPasswordPath = "/new-password/"; export interface NewPasswordUrlQueryParams { + email: string; token: string; } export const newPasswordUrl = (params?: NewPasswordUrlQueryParams) => diff --git a/src/auth/utils.ts b/src/auth/utils.ts new file mode 100644 index 000000000..f2ed57e85 --- /dev/null +++ b/src/auth/utils.ts @@ -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); +}; diff --git a/src/auth/views/NewPassword.tsx b/src/auth/views/NewPassword.tsx index e69de29bb..c667f92d0 100644 --- a/src/auth/views/NewPassword.tsx +++ b/src/auth/views/NewPassword.tsx @@ -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 = ({ 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 ( + + {(setPassword, setPasswordOpts) => { + const handleSubmit = (data: NewPasswordPageFormData) => + setPassword({ + variables: { + email: params.email, + password: data.password, + token: params.token + } + }); + + return ( + + ); + }} + + ); +}; + +NewPassword.displayName = "NewPassword"; +export default NewPassword; diff --git a/src/auth/views/ResetPassword.tsx b/src/auth/views/ResetPassword.tsx index 6ac2dab5c..3ba7fffa4 100644 --- a/src/auth/views/ResetPassword.tsx +++ b/src/auth/views/ResetPassword.tsx @@ -1,8 +1,8 @@ import React from "react"; import urlJoin from "url-join"; +import { APP_MOUNT_URI } from "@saleor/config"; import useNavigator from "@saleor/hooks/useNavigator"; -import useShop from "@saleor/hooks/useShop"; import ResetPasswordPage, { ResetPasswordPageFormData } from "../components/ResetPasswordPage"; @@ -12,7 +12,6 @@ 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) { @@ -27,7 +26,11 @@ const ResetPasswordView: React.FC = () => { requestPasswordReset({ variables: { email: data.email, - redirectUrl: urlJoin(shop.domain.url, newPasswordUrl()) + redirectUrl: urlJoin( + window.location.origin, + APP_MOUNT_URI, + newPasswordUrl().replace(/\?/, "") + ) } }); diff --git a/src/auth/views/ResetPasswordSuccess.tsx b/src/auth/views/ResetPasswordSuccess.tsx index 58f48de94..650a22cd4 100644 --- a/src/auth/views/ResetPasswordSuccess.tsx +++ b/src/auth/views/ResetPasswordSuccess.tsx @@ -2,12 +2,11 @@ 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 navigate(loginUrl)} />; + return navigate("/")} />; }; ResetPasswordSuccessView.displayName = "ResetPasswordSuccessView"; export default ResetPasswordSuccessView; diff --git a/src/index.tsx b/src/index.tsx index 1782f96ee..bf8e504e8 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -63,6 +63,7 @@ const invalidTokenLink = onError((error: ResponseError) => { const authLink = setContext((_, context) => { const authToken = getAuthToken(); + return { ...context, headers: { From 92788a2d96dbc5a08196908f102268569b2e6326 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Tue, 3 Sep 2019 15:42:49 +0200 Subject: [PATCH 06/15] Fix story --- .../ResetPasswordSuccessPage.stories.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/auth/components/ResetPasswordSuccessPage/ResetPasswordSuccessPage.stories.tsx b/src/auth/components/ResetPasswordSuccessPage/ResetPasswordSuccessPage.stories.tsx index 4edb5455b..771286d39 100644 --- a/src/auth/components/ResetPasswordSuccessPage/ResetPasswordSuccessPage.stories.tsx +++ b/src/auth/components/ResetPasswordSuccessPage/ResetPasswordSuccessPage.stories.tsx @@ -6,6 +6,4 @@ import ResetPasswordSuccessPage from "./ResetPasswordSuccessPage"; storiesOf("Views / Authentication / Reset password success", module) .addDecorator(Decorator) - .add("default", () => ( - undefined} /> - )); + .add("default", () => undefined} />); From 6b202edec702bf165d4e10a90224d134480abd53 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Tue, 3 Sep 2019 15:45:10 +0200 Subject: [PATCH 07/15] Update snapshots --- .../__snapshots__/Stories.test.ts.snap | 1167 +++++++++++------ 1 file changed, 757 insertions(+), 410 deletions(-) diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index eda2d6d28..c9094a92e 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -11912,149 +11912,135 @@ exports[`Storyshots Views / Authentication / Log in default 1`] = ` >
-
- +
+ +
-
+
+
+