From a1b9bf8d4e3f528e0ded1cc1edcefa051f7f956f Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 30 Sep 2019 12:32:00 +0200 Subject: [PATCH] Add token operations --- src/intl.ts | 4 + .../ServiceTokenCreateDialog.tsx | 162 ++++++++++++ .../ServiceTokenCreateDialog/index.ts | 2 + .../ServiceTokenDeleteDialog.stories.tsx | 19 ++ .../ServiceTokenDeleteDialog.tsx | 51 ++++ .../ServiceTokenDeleteDialog/index.ts | 2 + .../ServiceTokens/ServiceTokens.tsx | 5 +- src/services/fixtures.ts | 2 +- src/services/mutations.ts | 39 +++ src/services/types/ServiceTokenCreate.ts | 29 +++ src/services/types/ServiceTokenDelete.ts | 26 ++ .../views/ServiceDetails/ServiceDetails.tsx | 241 +++++++++++++----- .../views/ServiceList/ServiceList.tsx | 1 - src/types/globalTypes.ts | 5 + 14 files changed, 515 insertions(+), 73 deletions(-) create mode 100644 src/services/components/ServiceTokenCreateDialog/ServiceTokenCreateDialog.tsx create mode 100644 src/services/components/ServiceTokenCreateDialog/index.ts create mode 100644 src/services/components/ServiceTokenDeleteDialog/ServiceTokenDeleteDialog.stories.tsx create mode 100644 src/services/components/ServiceTokenDeleteDialog/ServiceTokenDeleteDialog.tsx create mode 100644 src/services/components/ServiceTokenDeleteDialog/index.ts create mode 100644 src/services/types/ServiceTokenCreate.ts create mode 100644 src/services/types/ServiceTokenDelete.ts diff --git a/src/intl.ts b/src/intl.ts index cbe7327a6..a15b105a1 100644 --- a/src/intl.ts +++ b/src/intl.ts @@ -88,6 +88,10 @@ export const buttonMessages = defineMessages({ defaultMessage: "Delete", description: "button" }, + done: { + defaultMessage: "Done", + description: "button" + }, edit: { defaultMessage: "Edit", description: "button" diff --git a/src/services/components/ServiceTokenCreateDialog/ServiceTokenCreateDialog.tsx b/src/services/components/ServiceTokenCreateDialog/ServiceTokenCreateDialog.tsx new file mode 100644 index 000000000..ed9a74f75 --- /dev/null +++ b/src/services/components/ServiceTokenCreateDialog/ServiceTokenCreateDialog.tsx @@ -0,0 +1,162 @@ +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 DialogTitle from "@material-ui/core/DialogTitle"; +import Paper from "@material-ui/core/Paper"; +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 { fade } from "@material-ui/core/styles/colorManipulator"; +import CardSpacer from "@saleor/components/CardSpacer"; +import ConfirmButton, { + ConfirmButtonTransitionState +} from "@saleor/components/ConfirmButton"; +import Form from "@saleor/components/Form"; +import FormSpacer from "@saleor/components/FormSpacer"; +import useModalDialogOpen from "@saleor/hooks/useModalDialogOpen"; +import { buttonMessages } from "@saleor/intl"; + +export interface ServiceTokenCreateDialogProps { + confirmButtonState: ConfirmButtonTransitionState; + open: boolean; + token: string | undefined; + onClose: () => void; + onCreate: (name: string) => void; +} + +type ServiceTokenCreateStep = "form" | "summary"; + +const useStyles = makeStyles( + (theme: Theme) => ({ + cancel: { + marginRight: theme.spacing.unit + }, + copy: { + position: "relative", + right: theme.spacing.unit + }, + paper: { + background: fade(theme.palette.primary.main, 0.05), + padding: `${theme.spacing.unit * 2}px ${theme.spacing.unit * 3}px` + } + }), + { + name: "ServiceTokenCreateDialog" + } +); + +const ServiceTokenCreateDialog: React.FC< + ServiceTokenCreateDialogProps +> = props => { + const { confirmButtonState, open, token, onClose, onCreate } = props; + const [step, setStep] = React.useState("form"); + const intl = useIntl(); + const classes = useStyles(props); + + React.useEffect(() => { + if (token !== undefined) { + setStep("summary"); + } + }, [token]); + + useModalDialogOpen(open, { + onClose: () => setStep("form") + }); + + return ( + +
onCreate(data.name)}> + {({ change, data, submit }) => { + const handleCopy = () => navigator.clipboard.writeText(token); + + return ( + <> + + + + + {step === "form" ? ( + <> + + + + + + + ) : ( + <> + + + + + + + + + {token} + + {" "} + + )} + + + {step === "form" ? ( + <> + + + + + + ) : ( + + )} + + + ); + }} +
+
+ ); +}; + +ServiceTokenCreateDialog.displayName = "ServiceTokenCreateDialog"; +export default ServiceTokenCreateDialog; diff --git a/src/services/components/ServiceTokenCreateDialog/index.ts b/src/services/components/ServiceTokenCreateDialog/index.ts new file mode 100644 index 000000000..f34be97d4 --- /dev/null +++ b/src/services/components/ServiceTokenCreateDialog/index.ts @@ -0,0 +1,2 @@ +export { default } from "./ServiceTokenCreateDialog"; +export * from "./ServiceTokenCreateDialog"; diff --git a/src/services/components/ServiceTokenDeleteDialog/ServiceTokenDeleteDialog.stories.tsx b/src/services/components/ServiceTokenDeleteDialog/ServiceTokenDeleteDialog.stories.tsx new file mode 100644 index 000000000..d1fcc2641 --- /dev/null +++ b/src/services/components/ServiceTokenDeleteDialog/ServiceTokenDeleteDialog.stories.tsx @@ -0,0 +1,19 @@ +import { storiesOf } from "@storybook/react"; +import React from "react"; + +import Decorator from "@saleor/storybook/Decorator"; +import ServiceTokenDeleteDialog, { + ServiceTokenDeleteDialogProps +} from "./ServiceTokenDeleteDialog"; + +const props: ServiceTokenDeleteDialogProps = { + confirmButtonState: "default", + name: "Slack", + onClose: () => undefined, + onConfirm: () => undefined, + open: true +}; + +storiesOf("Views / Services / Token delete", module) + .addDecorator(Decorator) + .add("default", () => ); diff --git a/src/services/components/ServiceTokenDeleteDialog/ServiceTokenDeleteDialog.tsx b/src/services/components/ServiceTokenDeleteDialog/ServiceTokenDeleteDialog.tsx new file mode 100644 index 000000000..a7dd61d86 --- /dev/null +++ b/src/services/components/ServiceTokenDeleteDialog/ServiceTokenDeleteDialog.tsx @@ -0,0 +1,51 @@ +import DialogContentText from "@material-ui/core/DialogContentText"; +import React from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import ActionDialog from "@saleor/components/ActionDialog"; +import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton"; + +export interface ServiceTokenDeleteDialogProps { + confirmButtonState: ConfirmButtonTransitionState; + open: boolean; + onConfirm: () => void; + onClose: () => void; + name: string; +} + +const ServiceTokenDeleteDialog: React.FC = ({ + name, + confirmButtonState, + onClose, + onConfirm, + open +}) => { + const intl = useIntl(); + + return ( + + + {name} + }} + /> + + + ); +}; + +ServiceTokenDeleteDialog.displayName = "ServiceTokenDeleteDialog"; +export default ServiceTokenDeleteDialog; diff --git a/src/services/components/ServiceTokenDeleteDialog/index.ts b/src/services/components/ServiceTokenDeleteDialog/index.ts new file mode 100644 index 000000000..8b3a70a9b --- /dev/null +++ b/src/services/components/ServiceTokenDeleteDialog/index.ts @@ -0,0 +1,2 @@ +export { default } from "./ServiceTokenDeleteDialog"; +export * from "./ServiceTokenDeleteDialog"; diff --git a/src/services/components/ServiceTokens/ServiceTokens.tsx b/src/services/components/ServiceTokens/ServiceTokens.tsx index 5949e3b62..8258808dd 100644 --- a/src/services/components/ServiceTokens/ServiceTokens.tsx +++ b/src/services/components/ServiceTokens/ServiceTokens.tsx @@ -94,7 +94,10 @@ const ServiceTokens: React.FC = props => { {maybe(() => token.name, )} - {maybe(() => token.authToken, )} + {maybe( + () => `**** ${token.authToken}`, + + )} (serviceUpdateMutation); + +const serviceTokenCreate = gql` + mutation ServiceTokenCreate($input: ServiceAccountTokenInput!) { + serviceAccountTokenCreate(input: $input) { + errors { + field + message + } + authToken + } + } +`; +export const ServiceTokenCreateMutation = TypedMutation< + ServiceTokenCreate, + ServiceTokenCreateVariables +>(serviceTokenCreate); + +const serviceTokenDelete = gql` + mutation ServiceTokenDelete($id: ID!) { + serviceAccountTokenDelete(id: $id) { + errors { + field + message + } + } + } +`; +export const ServiceTokenDeleteMutation = TypedMutation< + ServiceTokenDelete, + ServiceTokenDeleteVariables +>(serviceTokenDelete); diff --git a/src/services/types/ServiceTokenCreate.ts b/src/services/types/ServiceTokenCreate.ts new file mode 100644 index 000000000..8d6ba0ed3 --- /dev/null +++ b/src/services/types/ServiceTokenCreate.ts @@ -0,0 +1,29 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { ServiceAccountTokenInput } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL mutation operation: ServiceTokenCreate +// ==================================================== + +export interface ServiceTokenCreate_serviceAccountTokenCreate_errors { + __typename: "Error"; + field: string | null; + message: string | null; +} + +export interface ServiceTokenCreate_serviceAccountTokenCreate { + __typename: "ServiceAccountTokenCreate"; + errors: ServiceTokenCreate_serviceAccountTokenCreate_errors[] | null; + authToken: string | null; +} + +export interface ServiceTokenCreate { + serviceAccountTokenCreate: ServiceTokenCreate_serviceAccountTokenCreate | null; +} + +export interface ServiceTokenCreateVariables { + input: ServiceAccountTokenInput; +} diff --git a/src/services/types/ServiceTokenDelete.ts b/src/services/types/ServiceTokenDelete.ts new file mode 100644 index 000000000..d7bc83ead --- /dev/null +++ b/src/services/types/ServiceTokenDelete.ts @@ -0,0 +1,26 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL mutation operation: ServiceTokenDelete +// ==================================================== + +export interface ServiceTokenDelete_serviceAccountTokenDelete_errors { + __typename: "Error"; + field: string | null; + message: string | null; +} + +export interface ServiceTokenDelete_serviceAccountTokenDelete { + __typename: "ServiceAccountTokenDelete"; + errors: ServiceTokenDelete_serviceAccountTokenDelete_errors[] | null; +} + +export interface ServiceTokenDelete { + serviceAccountTokenDelete: ServiceTokenDelete_serviceAccountTokenDelete | null; +} + +export interface ServiceTokenDeleteVariables { + id: string; +} diff --git a/src/services/views/ServiceDetails/ServiceDetails.tsx b/src/services/views/ServiceDetails/ServiceDetails.tsx index 990fb766e..cd3433b77 100644 --- a/src/services/views/ServiceDetails/ServiceDetails.tsx +++ b/src/services/views/ServiceDetails/ServiceDetails.tsx @@ -8,11 +8,16 @@ import useShop from "@saleor/hooks/useShop"; import { commonMessages } from "@saleor/intl"; import { getMutationState, maybe } from "@saleor/misc"; import ServiceDeleteDialog from "@saleor/services/components/ServiceDeleteDialog"; +import ServiceTokenCreateDialog from "@saleor/services/components/ServiceTokenCreateDialog"; +import ServiceTokenDeleteDialog from "@saleor/services/components/ServiceTokenDeleteDialog"; import { ServiceDeleteMutation, + ServiceTokenCreateMutation, + ServiceTokenDeleteMutation, ServiceUpdateMutation } from "@saleor/services/mutations"; import { ServiceDelete } from "@saleor/services/types/ServiceDelete"; +import { ServiceTokenDelete } from "@saleor/services/types/ServiceTokenDelete"; import { ServiceUpdate } from "@saleor/services/types/ServiceUpdate"; import ServiceDetailsPage, { ServiceDetailsPageFormData @@ -59,7 +64,7 @@ export const ServiceDetails: React.StatelessComponent = ({ ); const onServiceUpdate = (data: ServiceUpdate) => { - if (!maybe(() => data.serviceAccountUpdate.errors.length !== 0)) { + if (maybe(() => data.serviceAccountUpdate.errors.length === 0)) { notify({ text: intl.formatMessage(commonMessages.savedChanges) }); @@ -82,79 +87,175 @@ export const ServiceDetails: React.StatelessComponent = ({ variables={{ id }} require={["serviceAccount"]} > - {({ data, loading }) => ( - - {(updateService, updateServiceOpts) => ( - - {(deleteService, deleteServiceOpts) => { - const handleSubmit = (data: ServiceDetailsPageFormData) => - updateService({ - variables: { - id, - input: { - isActive: data.isActive, - name: data.name, - permissions: data.hasFullAccess - ? shop.permissions.map(permission => permission.code) - : data.permissions - } - } - }); + {({ data, loading, refetch }) => { + const onTokenDelete = (data: ServiceTokenDelete) => { + if (maybe(() => data.serviceAccountTokenDelete.errors.length === 0)) { + notify({ + text: intl.formatMessage(commonMessages.savedChanges) + }); + refetch(); + closeModal(); + } + }; + return ( + + {(updateService, updateServiceOpts) => ( + + {(deleteService, deleteServiceOpts) => ( + + {(createToken, createTokenOpts) => ( + + {(deleteToken, deleteTokenOpts) => { + const handleSubmit = ( + data: ServiceDetailsPageFormData + ) => + updateService({ + variables: { + id, + input: { + isActive: data.isActive, + name: data.name, + permissions: data.hasFullAccess + ? shop.permissions.map( + permission => permission.code + ) + : data.permissions + } + } + }); - const handleRemoveConfirm = () => - deleteService({ - variables: { - id - } - }); + const handleRemoveConfirm = () => + deleteService({ + variables: { + id + } + }); - const formTransitionState = getMutationState( - updateServiceOpts.called, - updateServiceOpts.loading, - maybe( - () => updateServiceOpts.data.serviceAccountUpdate.errors - ) - ); + const handleTokenCreate = (name: string) => + createToken({ + variables: { + input: { + name, + serviceAccount: id + } + } + }); - const deleteTransitionState = getMutationState( - deleteServiceOpts.called, - deleteServiceOpts.loading, - maybe( - () => deleteServiceOpts.data.serviceAccountDelete.errors - ) - ); + const handleTokenDelete = () => + deleteToken({ + variables: { + id: params.id + } + }); - return ( - <> - data.serviceAccount.name)} - /> - openModal("remove")} - onSubmit={handleSubmit} - onTokenCreate={() => openModal("create-token")} - onTokenDelete={() => openModal("remove-token")} - permissions={maybe(() => shop.permissions)} - service={maybe(() => data.serviceAccount)} - saveButtonBarState={formTransitionState} - /> - data.serviceAccount.name, "...")} - onClose={closeModal} - onConfirm={handleRemoveConfirm} - open={params.action === "remove"} - /> - - ); - }} - - )} - - )} + const formTransitionState = getMutationState( + updateServiceOpts.called, + updateServiceOpts.loading, + maybe( + () => + updateServiceOpts.data.serviceAccountUpdate + .errors + ) + ); + + const deleteTransitionState = getMutationState( + deleteServiceOpts.called, + deleteServiceOpts.loading, + maybe( + () => + deleteServiceOpts.data.serviceAccountDelete + .errors + ) + ); + + const createTokenTransitionState = getMutationState( + createTokenOpts.called, + createTokenOpts.loading, + maybe( + () => + createTokenOpts.data.serviceAccountTokenCreate + .errors + ) + ); + + const deleteTokenTransitionState = getMutationState( + deleteTokenOpts.called, + deleteTokenOpts.loading, + maybe( + () => + deleteTokenOpts.data.serviceAccountTokenDelete + .errors + ) + ); + + return ( + <> + data.serviceAccount.name)} + /> + openModal("remove")} + onSubmit={handleSubmit} + onTokenCreate={() => openModal("create-token")} + onTokenDelete={id => + openModal("remove-token", id) + } + permissions={maybe(() => shop.permissions)} + service={maybe(() => data.serviceAccount)} + saveButtonBarState={formTransitionState} + /> + data.serviceAccount.name, + "..." + )} + onClose={closeModal} + onConfirm={handleRemoveConfirm} + open={params.action === "remove"} + /> + + createTokenOpts.data + .serviceAccountTokenCreate.authToken + )} + /> + { + const token = data.serviceAccount.tokens.find( + token => token.id === params.id + ); + if (token.name) { + return token.name; + } + + return `**** ${token.authToken}`; + }, "...")} + onClose={closeModal} + onConfirm={handleTokenDelete} + open={params.action === "remove-token"} + /> + + ); + }} + + )} + + )} + + )} + + ); + }} ); }; diff --git a/src/services/views/ServiceList/ServiceList.tsx b/src/services/views/ServiceList/ServiceList.tsx index 6e102fd16..fca351511 100644 --- a/src/services/views/ServiceList/ServiceList.tsx +++ b/src/services/views/ServiceList/ServiceList.tsx @@ -13,7 +13,6 @@ import SaveFilterTabDialog, { SaveFilterTabDialogFormData } from "@saleor/components/SaveFilterTabDialog"; import { configurationMenuUrl } from "@saleor/configuration"; -import useShop from "@saleor/hooks/useShop"; import { commonMessages } from "@saleor/intl"; import { getMutationState, maybe } from "@saleor/misc"; import { ServiceDeleteMutation } from "@saleor/services/mutations"; diff --git a/src/types/globalTypes.ts b/src/types/globalTypes.ts index cf5449769..b18eee0ac 100644 --- a/src/types/globalTypes.ts +++ b/src/types/globalTypes.ts @@ -661,6 +661,11 @@ export interface ServiceAccountInput { permissions?: (PermissionEnum | null)[] | null; } +export interface ServiceAccountTokenInput { + name?: string | null; + serviceAccount: string; +} + export interface ShippingPriceInput { name?: string | null; price?: any | null;