diff --git a/CHANGELOG.md b/CHANGELOG.md index b2430dd3f..f8144c1ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ All notable, unreleased changes to this project will be documented in this file. - Fix dropdown clickable areas - #281 by @dominik-zeglen - Use eslint - #285 by @dominik-zeglen - Enforce using "name" property in style hooks - #288 by @dominik-zeglen +- Add ability to reset own password - #289 by @dominik-zeglen ## 2.0.0 diff --git a/locale/messages.pot b/locale/messages.pot index 28902c762..2d58e0b2e 100644 --- a/locale/messages.pot +++ b/locale/messages.pot @@ -1,6 +1,6 @@ msgid "" msgstr "" -"POT-Creation-Date: 2019-11-26T14:34:48.426Z\n" +"POT-Creation-Date: 2019-12-04T14:17:34.264Z\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "MIME-Version: 1.0\n" @@ -1855,6 +1855,14 @@ msgctxt "description" msgid "Category name" msgstr "" +#: build/locale/src/staff/components/StaffPasswordResetDialog/StaffPasswordResetDialog.json +#. [src.staff.components.StaffPasswordResetDialog.2521568990] - dialog header +#. defaultMessage is: +#. Change Password +msgctxt "dialog header" +msgid "Change Password" +msgstr "" + #: build/locale/src/staff/components/StaffProperties/StaffProperties.json #. [src.staff.components.StaffProperties.2771097267] - button #. defaultMessage is: @@ -1863,6 +1871,14 @@ msgctxt "button" msgid "Change photo" msgstr "" +#: build/locale/src/staff/components/StaffPassword/StaffPassword.json +#. [src.staff.components.StaffPassword.1434811103] - button +#. defaultMessage is: +#. Change your password +msgctxt "button" +msgid "Change your password" +msgstr "" + #: build/locale/src/products/components/ProductPricing/ProductPricing.json #. [src.products.components.ProductPricing.3015886868] #. defaultMessage is: @@ -4911,6 +4927,14 @@ msgctxt "description" msgid "New Password" msgstr "" +#: build/locale/src/staff/components/StaffPasswordResetDialog/StaffPasswordResetDialog.json +#. [src.staff.components.StaffPasswordResetDialog.1254879564] - input label +#. defaultMessage is: +#. New Password +msgctxt "input label" +msgid "New Password" +msgstr "" + #: build/locale/src/products/views/ProductCreate.json #. [src.products.views.1591632382] - page header #. defaultMessage is: @@ -4927,6 +4951,14 @@ msgctxt "variant name" msgid "New Variant" msgstr "" +#: build/locale/src/staff/components/StaffPasswordResetDialog/StaffPasswordResetDialog.json +#. [src.staff.components.StaffPasswordResetDialog.1651415182] +#. defaultMessage is: +#. New password must be at least 8 characters long +msgctxt "description" +msgid "New password must be at least 8 characters long" +msgstr "" + #: build/locale/src/taxes/components/CountryTaxesPage/CountryTaxesPage.json #. [src.taxes.components.CountryTaxesPage.1451721797] - tax rate #. defaultMessage is: @@ -5947,6 +5979,14 @@ msgctxt "description" msgid "Password" msgstr "" +#: build/locale/src/staff/components/StaffPassword/StaffPassword.json +#. [src.staff.components.StaffPassword.2237029987] - header +#. defaultMessage is: +#. Password +msgctxt "header" +msgid "Password" +msgstr "" + #: build/locale/src/auth/components/NewPasswordPage/NewPasswordPage.json #. [src.auth.components.NewPasswordPage.4253911811] #. defaultMessage is: @@ -6215,6 +6255,14 @@ msgctxt "previous step, button" msgid "Previous" msgstr "" +#: build/locale/src/staff/components/StaffPasswordResetDialog/StaffPasswordResetDialog.json +#. [src.staff.components.StaffPasswordResetDialog.53359254] - input label +#. defaultMessage is: +#. Previous Password +msgctxt "input label" +msgid "Previous Password" +msgstr "" + #: build/locale/src/categories/components/CategoryProductList/CategoryProductList.json #. [src.categories.components.CategoryProductList.1134347598] - product price #. defaultMessage is: @@ -9667,6 +9715,14 @@ msgctxt "description" msgid "Yes" msgstr "" +#: build/locale/src/staff/components/StaffPassword/StaffPassword.json +#. [src.staff.components.StaffPassword.1274006906] +#. defaultMessage is: +#. You should change your password every month to avoid security issues. +msgctxt "description" +msgid "You should change your password every month to avoid security issues." +msgstr "" + #: build/locale/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.json #. [src.products.components.ProductVariantCreateDialog.1009678918] - header #. defaultMessage is: diff --git a/src/staff/components/StaffDetailsPage/StaffDetailsPage.tsx b/src/staff/components/StaffDetailsPage/StaffDetailsPage.tsx index f8e1d9b80..dfb3ae516 100644 --- a/src/staff/components/StaffDetailsPage/StaffDetailsPage.tsx +++ b/src/staff/components/StaffDetailsPage/StaffDetailsPage.tsx @@ -19,6 +19,7 @@ import { PermissionEnum } from "../../../types/globalTypes"; import { StaffMemberDetails_user } from "../../types/StaffMemberDetails"; import StaffPreferences from "../StaffPreferences"; import StaffProperties from "../StaffProperties/StaffProperties"; +import StaffPassword from "../StaffPassword/StaffPassword"; interface FormData { hasFullAccess: boolean; @@ -39,6 +40,7 @@ export interface StaffDetailsPageProps { saveButtonBarState: ConfirmButtonTransitionState; staffMember: StaffMemberDetails_user; onBack: () => void; + onChangePassword: () => void; onDelete: () => void; onImageDelete: () => void; onSubmit: (data: FormData) => void; @@ -55,6 +57,7 @@ const StaffDetailsPage: React.FC = ({ saveButtonBarState, staffMember, onBack, + onChangePassword, onDelete, onImageDelete, onImageUpload, @@ -100,6 +103,12 @@ const StaffDetailsPage: React.FC = ({ onImageUpload={onImageUpload} onImageDelete={onImageDelete} /> + {canEditPreferences && ( + <> + + + + )}
{canEditPreferences && ( diff --git a/src/staff/components/StaffPassword/StaffPassword.tsx b/src/staff/components/StaffPassword/StaffPassword.tsx new file mode 100644 index 000000000..1d9582540 --- /dev/null +++ b/src/staff/components/StaffPassword/StaffPassword.tsx @@ -0,0 +1,43 @@ +import React from "react"; +import Button from "@material-ui/core/Button"; +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import Typography from "@material-ui/core/Typography"; +import { FormattedMessage, useIntl } from "react-intl"; + +import CardTitle from "@saleor/components/CardTitle"; + +interface StaffPasswordProps { + onChangePassword: () => void; +} + +const StaffPassword: React.FC = ({ onChangePassword }) => { + const intl = useIntl(); + + return ( + + + + + } + /> + + + + + + + ); +}; + +StaffPassword.displayName = "StaffPassword"; +export default StaffPassword; diff --git a/src/staff/components/StaffPassword/index.ts b/src/staff/components/StaffPassword/index.ts new file mode 100644 index 000000000..9131e4472 --- /dev/null +++ b/src/staff/components/StaffPassword/index.ts @@ -0,0 +1,2 @@ +export { default } from "./StaffPassword"; +export * from "./StaffPassword"; diff --git a/src/staff/components/StaffPasswordResetDialog/StaffPasswordResetDialog.tsx b/src/staff/components/StaffPasswordResetDialog/StaffPasswordResetDialog.tsx new file mode 100644 index 000000000..93420361b --- /dev/null +++ b/src/staff/components/StaffPasswordResetDialog/StaffPasswordResetDialog.tsx @@ -0,0 +1,111 @@ +import React from "react"; +import Button from "@material-ui/core/Button"; +import Dialog from "@material-ui/core/Dialog"; +import DialogActions from "@material-ui/core/DialogActions"; +import DialogContent from "@material-ui/core/DialogContent"; +import TextField from "@material-ui/core/TextField"; +import DialogTitle from "@material-ui/core/DialogTitle"; +import { FormattedMessage, useIntl } from "react-intl"; + +import { DialogProps, UserError } from "@saleor/types"; +import { buttonMessages } from "@saleor/intl"; +import Form from "@saleor/components/Form"; +import ConfirmButton, { + ConfirmButtonTransitionState +} from "@saleor/components/ConfirmButton"; +import FormSpacer from "@saleor/components/FormSpacer"; +import useModalDialogErrors from "@saleor/hooks/useModalDialogErrors"; + +interface StaffPasswordResetDialogFormData { + newPassword: string; + oldPassword: string; +} +export interface StaffPasswordResetDialogProps extends DialogProps { + confirmButtonState: ConfirmButtonTransitionState; + errors: UserError[]; + onSubmit: (data: StaffPasswordResetDialogFormData) => void; +} + +const initialForm: StaffPasswordResetDialogFormData = { + newPassword: "", + oldPassword: "" +}; + +const StaffPasswordResetDialog: React.FC = ({ + confirmButtonState, + errors: apiErrors, + open, + onClose, + onSubmit +}) => { + const intl = useIntl(); + const dialogErrors = useModalDialogErrors(apiErrors, open); + + return ( + + + + +
+ {({ change, data, errors, submit }) => ( + <> + + + + + + + + + + + + + )} +
+
+ ); +}; + +StaffPasswordResetDialog.displayName = "StaffPasswordResetDialog"; +export default StaffPasswordResetDialog; diff --git a/src/staff/components/StaffPasswordResetDialog/index.ts b/src/staff/components/StaffPasswordResetDialog/index.ts new file mode 100644 index 000000000..4bffd3362 --- /dev/null +++ b/src/staff/components/StaffPasswordResetDialog/index.ts @@ -0,0 +1,2 @@ +export { default } from "./StaffPasswordResetDialog"; +export * from "./StaffPasswordResetDialog"; diff --git a/src/staff/mutations.ts b/src/staff/mutations.ts index a3939ecc3..ee1dde297 100644 --- a/src/staff/mutations.ts +++ b/src/staff/mutations.ts @@ -1,5 +1,6 @@ import gql from "graphql-tag"; +import makeMutation from "@saleor/hooks/makeMutation"; import { TypedMutation } from "../mutations"; import { staffMemberDetailsFragment } from "./queries"; import { StaffAvatarDelete } from "./types/StaffAvatarDelete"; @@ -19,6 +20,10 @@ import { StaffMemberUpdate, StaffMemberUpdateVariables } from "./types/StaffMemberUpdate"; +import { + ChangeStaffPassword, + ChangeStaffPasswordVariables +} from "./types/ChangeStaffPassword"; const staffMemberAddMutation = gql` ${staffMemberDetailsFragment} @@ -114,3 +119,18 @@ export const TypedStaffAvatarDeleteMutation = TypedMutation< StaffAvatarDelete, StaffMemberDeleteVariables >(staffAvatarDeleteMutation); + +const changeStaffPassword = gql` + mutation ChangeStaffPassword($newPassword: String!, $oldPassword: String!) { + passwordChange(newPassword: $newPassword, oldPassword: $oldPassword) { + errors { + field + message + } + } + } +`; +export const useChangeStaffPassword = makeMutation< + ChangeStaffPassword, + ChangeStaffPasswordVariables +>(changeStaffPassword); diff --git a/src/staff/types/ChangeStaffPassword.ts b/src/staff/types/ChangeStaffPassword.ts new file mode 100644 index 000000000..18c15db12 --- /dev/null +++ b/src/staff/types/ChangeStaffPassword.ts @@ -0,0 +1,27 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL mutation operation: ChangeStaffPassword +// ==================================================== + +export interface ChangeStaffPassword_passwordChange_errors { + __typename: "Error"; + field: string | null; + message: string | null; +} + +export interface ChangeStaffPassword_passwordChange { + __typename: "PasswordChange"; + errors: ChangeStaffPassword_passwordChange_errors[] | null; +} + +export interface ChangeStaffPassword { + passwordChange: ChangeStaffPassword_passwordChange | null; +} + +export interface ChangeStaffPasswordVariables { + newPassword: string; + oldPassword: string; +} diff --git a/src/staff/urls.ts b/src/staff/urls.ts index 7bdf6e201..b99a09235 100644 --- a/src/staff/urls.ts +++ b/src/staff/urls.ts @@ -27,7 +27,10 @@ export const staffListUrl = (params?: StaffListUrlQueryParams) => staffListPath + "?" + stringifyQs(params); export const staffMemberDetailsPath = (id: string) => urlJoin(staffSection, id); -export type StaffMemberDetailsUrlDialog = "remove" | "remove-avatar"; +export type StaffMemberDetailsUrlDialog = + | "change-password" + | "remove" + | "remove-avatar"; export type StaffMemberDetailsUrlQueryParams = Dialog< StaffMemberDetailsUrlDialog >; diff --git a/src/staff/views/StaffDetails.tsx b/src/staff/views/StaffDetails.tsx index e3739efc0..85a9c4bc6 100644 --- a/src/staff/views/StaffDetails.tsx +++ b/src/staff/views/StaffDetails.tsx @@ -15,7 +15,8 @@ import { TypedStaffAvatarDeleteMutation, TypedStaffAvatarUpdateMutation, TypedStaffMemberDeleteMutation, - TypedStaffMemberUpdateMutation + TypedStaffMemberUpdateMutation, + useChangeStaffPassword } from "../mutations"; import { TypedStaffMemberDetailsQuery } from "../queries"; import { StaffAvatarDelete } from "../types/StaffAvatarDelete"; @@ -27,6 +28,8 @@ import { staffMemberDetailsUrl, StaffMemberDetailsUrlQueryParams } from "../urls"; +import StaffPasswordResetDialog from "../components/StaffPasswordResetDialog"; +import { ChangeStaffPassword } from "../types/ChangeStaffPassword"; interface OrderListProps { id: string; @@ -40,6 +43,32 @@ export const StaffDetails: React.FC = ({ id, params }) => { const intl = useIntl(); const shop = useShop(); + const closeModal = () => + navigate( + staffMemberDetailsUrl(id, { + ...params, + action: undefined + }) + ); + + const handleChangePassword = (data: ChangeStaffPassword) => { + if (data.passwordChange.errors.length === 0) { + notify({ + text: intl.formatMessage(commonMessages.savedChanges) + }); + closeModal(); + } + }; + const [changePassword, changePasswordOpts] = useChangeStaffPassword({ + onCompleted: handleChangePassword + }); + + const changePasswordTransitionState = getMutationState( + changePasswordOpts.called, + changePasswordOpts.loading, + maybe(() => changePasswordOpts.data.passwordChange.errors) + ); + return ( = ({ id, params }) => { canRemove={!isUserSameAsViewer} disabled={loading} onBack={() => navigate(staffListUrl())} + onChangePassword={() => + navigate( + staffMemberDetailsUrl(id, { + action: "change-password" + }) + ) + } onDelete={() => navigate( staffMemberDetailsUrl(id, { @@ -175,9 +211,7 @@ export const StaffDetails: React.FC = ({ id, params }) => { })} confirmButtonState={deleteTransitionState} variant="delete" - onClose={() => - navigate(staffMemberDetailsUrl(id)) - } + onClose={closeModal} onConfirm={deleteStaffMember} > @@ -197,9 +231,7 @@ export const StaffDetails: React.FC = ({ id, params }) => { })} confirmButtonState={deleteAvatarTransitionState} variant="delete" - onClose={() => - navigate(staffMemberDetailsUrl(id)) - } + onClose={closeModal} onConfirm={deleteStaffAvatar} > @@ -215,6 +247,24 @@ export const StaffDetails: React.FC = ({ id, params }) => { /> + + changePasswordOpts.data.passwordChange + .errors, + [] + )} + open={params.action === "change-password"} + onClose={closeModal} + onSubmit={data => + changePassword({ + variables: data + }) + } + /> ); }} diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index ec1f35e51..a03cbb98b 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -121349,6 +121349,52 @@ exports[`Storyshots Views / Staff / Staff member details himself 1`] = `
+
+
+
+ + Password + +
+ +
+
+
+
+
+
+ You should change your password every month to avoid security issues. +
+
+
= { canRemove: true, disabled: false, onBack: () => undefined, + onChangePassword: () => undefined, onDelete: () => undefined, onImageDelete: () => undefined, onImageUpload: () => undefined,