Merge pull request #289 from mirumee/add/staff-password-reset

Add ability to reset own password
This commit is contained in:
Marcin Gębala 2019-12-04 15:30:28 +01:00 committed by GitHub
commit 7090148f52
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 380 additions and 9 deletions

View file

@ -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

View file

@ -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:

View file

@ -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<StaffDetailsPageProps> = ({
saveButtonBarState,
staffMember,
onBack,
onChangePassword,
onDelete,
onImageDelete,
onImageUpload,
@ -100,6 +103,12 @@ const StaffDetailsPage: React.FC<StaffDetailsPageProps> = ({
onImageUpload={onImageUpload}
onImageDelete={onImageDelete}
/>
{canEditPreferences && (
<>
<CardSpacer />
<StaffPassword onChangePassword={onChangePassword} />
</>
)}
</div>
<div>
{canEditPreferences && (

View file

@ -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<StaffPasswordProps> = ({ onChangePassword }) => {
const intl = useIntl();
return (
<Card>
<CardTitle
title={intl.formatMessage({
defaultMessage: "Password",
description: "header"
})}
toolbar={
<Button color="primary" onClick={onChangePassword}>
<FormattedMessage
defaultMessage="Change your password"
description="button"
/>
</Button>
}
/>
<CardContent>
<Typography>
<FormattedMessage defaultMessage="You should change your password every month to avoid security issues." />
</Typography>
</CardContent>
</Card>
);
};
StaffPassword.displayName = "StaffPassword";
export default StaffPassword;

View file

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

View file

@ -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<StaffPasswordResetDialogProps> = ({
confirmButtonState,
errors: apiErrors,
open,
onClose,
onSubmit
}) => {
const intl = useIntl();
const dialogErrors = useModalDialogErrors(apiErrors, open);
return (
<Dialog onClose={onClose} open={open} fullWidth maxWidth="sm">
<DialogTitle>
<FormattedMessage
defaultMessage="Change Password"
description="dialog header"
/>
</DialogTitle>
<Form errors={dialogErrors} initial={initialForm} onSubmit={onSubmit}>
{({ change, data, errors, submit }) => (
<>
<DialogContent>
<TextField
error={!!errors.oldPassword}
fullWidth
helperText={errors.oldPassword}
label={intl.formatMessage({
defaultMessage: "Previous Password",
description: "input label"
})}
name="oldPassword"
type="password"
onChange={change}
/>
<FormSpacer />
<TextField
error={!!errors.newPassword}
fullWidth
helperText={
errors.newPassword ||
intl.formatMessage({
defaultMessage:
"New password must be at least 8 characters long"
})
}
label={intl.formatMessage({
defaultMessage: "New Password",
description: "input label"
})}
name="newPassword"
type="password"
onChange={change}
/>
</DialogContent>
<DialogActions>
<Button onClick={onClose}>
<FormattedMessage {...buttonMessages.back} />
</Button>
<ConfirmButton
disabled={data.newPassword.length < 8}
transitionState={confirmButtonState}
color="primary"
variant="contained"
type="submit"
onClick={submit}
>
<FormattedMessage {...buttonMessages.save} />
</ConfirmButton>
</DialogActions>
</>
)}
</Form>
</Dialog>
);
};
StaffPasswordResetDialog.displayName = "StaffPasswordResetDialog";
export default StaffPasswordResetDialog;

View file

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

View file

@ -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);

View file

@ -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;
}

View file

@ -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
>;

View file

@ -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<OrderListProps> = ({ 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 (
<TypedStaffMemberDetailsQuery
displayLoader
@ -128,6 +157,13 @@ export const StaffDetails: React.FC<OrderListProps> = ({ 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<OrderListProps> = ({ id, params }) => {
})}
confirmButtonState={deleteTransitionState}
variant="delete"
onClose={() =>
navigate(staffMemberDetailsUrl(id))
}
onClose={closeModal}
onConfirm={deleteStaffMember}
>
<DialogContentText>
@ -197,9 +231,7 @@ export const StaffDetails: React.FC<OrderListProps> = ({ id, params }) => {
})}
confirmButtonState={deleteAvatarTransitionState}
variant="delete"
onClose={() =>
navigate(staffMemberDetailsUrl(id))
}
onClose={closeModal}
onConfirm={deleteStaffAvatar}
>
<DialogContentText>
@ -215,6 +247,24 @@ export const StaffDetails: React.FC<OrderListProps> = ({ id, params }) => {
/>
</DialogContentText>
</ActionDialog>
<StaffPasswordResetDialog
confirmButtonState={
changePasswordTransitionState
}
errors={maybe(
() =>
changePasswordOpts.data.passwordChange
.errors,
[]
)}
open={params.action === "change-password"}
onClose={closeModal}
onSubmit={data =>
changePassword({
variables: data
})
}
/>
</>
);
}}

View file

@ -121349,6 +121349,52 @@ exports[`Storyshots Views / Staff / Staff member details himself 1`] = `
</div>
</div>
</div>
<div
class="CardSpacer-spacer-id"
/>
<div
class="MuiPaper-root-id MuiPaper-elevation0-id MuiCard-root-id MuiPaper-rounded-id"
>
<div
class="CardTitle-root-id"
>
<span
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
Password
</span>
<div
class="CardTitle-toolbar-id"
>
<button
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-text-id MuiButton-textPrimary-id"
tabindex="0"
type="button"
>
<span
class="MuiButton-label-id"
>
Change your password
</span>
</button>
</div>
</div>
<div
class="CardTitle-children-id"
/>
<hr
class="CardTitle-hr-id"
/>
<div
class="MuiCardContent-root-id"
>
<div
class="MuiTypography-root-id MuiTypography-body1-id"
>
You should change your password every month to avoid security issues.
</div>
</div>
</div>
</div>
<div>
<div

View file

@ -16,6 +16,7 @@ const props: Omit<StaffDetailsPageProps, "classes"> = {
canRemove: true,
disabled: false,
onBack: () => undefined,
onChangePassword: () => undefined,
onDelete: () => undefined,
onImageDelete: () => undefined,
onImageUpload: () => undefined,