Add token operations

This commit is contained in:
dominik-zeglen 2019-09-30 12:32:00 +02:00
parent 043371685e
commit a1b9bf8d4e
14 changed files with 515 additions and 73 deletions

View file

@ -88,6 +88,10 @@ export const buttonMessages = defineMessages({
defaultMessage: "Delete", defaultMessage: "Delete",
description: "button" description: "button"
}, },
done: {
defaultMessage: "Done",
description: "button"
},
edit: { edit: {
defaultMessage: "Edit", defaultMessage: "Edit",
description: "button" description: "button"

View file

@ -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<ServiceTokenCreateStep>("form");
const intl = useIntl();
const classes = useStyles(props);
React.useEffect(() => {
if (token !== undefined) {
setStep("summary");
}
}, [token]);
useModalDialogOpen(open, {
onClose: () => setStep("form")
});
return (
<Dialog open={open} fullWidth maxWidth="sm">
<Form initial={{ name: "" }} onSubmit={data => onCreate(data.name)}>
{({ change, data, submit }) => {
const handleCopy = () => navigator.clipboard.writeText(token);
return (
<>
<DialogTitle>
<FormattedMessage
defaultMessage="Create Token"
description="header"
/>
</DialogTitle>
<DialogContent>
{step === "form" ? (
<>
<Typography>
<FormattedMessage defaultMessage="Access token is used to authenticate service accounts" />
</Typography>
<FormSpacer />
<TextField
label={intl.formatMessage({
defaultMessage: "Token Note"
})}
value={data.name}
onChange={change}
fullWidth
name="name"
/>
</>
) : (
<>
<Typography>
<FormattedMessage defaultMessage="Weve created your token. Make sure to copy your new personal access token now. You wont be able to see it again." />
</Typography>
<CardSpacer />
<Paper className={classes.paper} elevation={0}>
<Typography variant="caption">
<FormattedMessage defaultMessage="Generated Token" />
</Typography>
<Typography>{token}</Typography>
<Button
className={classes.copy}
color="primary"
onClick={handleCopy}
>
<FormattedMessage
defaultMessage="Copy token"
description="button"
/>
</Button>
</Paper>{" "}
</>
)}
</DialogContent>
<DialogActions>
{step === "form" ? (
<>
<Button
className={classes.cancel}
color="primary"
onClick={onClose}
>
<FormattedMessage {...buttonMessages.cancel} />
</Button>
<ConfirmButton
transitionState={confirmButtonState}
onClick={submit}
>
<FormattedMessage
defaultMessage="Create"
description="create service token, button"
/>
</ConfirmButton>
</>
) : (
<Button color="primary" variant="contained" onClick={onClose}>
<FormattedMessage {...buttonMessages.done} />
</Button>
)}
</DialogActions>
</>
);
}}
</Form>
</Dialog>
);
};
ServiceTokenCreateDialog.displayName = "ServiceTokenCreateDialog";
export default ServiceTokenCreateDialog;

View file

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

View file

@ -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", () => <ServiceTokenDeleteDialog {...props} />);

View file

@ -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<ServiceTokenDeleteDialogProps> = ({
name,
confirmButtonState,
onClose,
onConfirm,
open
}) => {
const intl = useIntl();
return (
<ActionDialog
open={open}
onClose={onClose}
confirmButtonState={confirmButtonState}
onConfirm={onConfirm}
variant="delete"
title={intl.formatMessage({
defaultMessage: "Delete Service Account",
description: "dialog title"
})}
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to delete token {token}?"
description="delete token"
values={{
token: <strong>{name}</strong>
}}
/>
</DialogContentText>
</ActionDialog>
);
};
ServiceTokenDeleteDialog.displayName = "ServiceTokenDeleteDialog";
export default ServiceTokenDeleteDialog;

View file

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

View file

@ -94,7 +94,10 @@ const ServiceTokens: React.FC<ServiceTokensProps> = props => {
{maybe<React.ReactNode>(() => token.name, <Skeleton />)} {maybe<React.ReactNode>(() => token.name, <Skeleton />)}
</TableCell> </TableCell>
<TableCell className={classes.colKey}> <TableCell className={classes.colKey}>
{maybe<React.ReactNode>(() => token.authToken, <Skeleton />)} {maybe<React.ReactNode>(
() => `**** ${token.authToken}`,
<Skeleton />
)}
</TableCell> </TableCell>
<TableCell className={classes.colActions}> <TableCell className={classes.colActions}>
<IconButton <IconButton

View file

@ -38,7 +38,7 @@ export const service: ServiceDetails_serviceAccount = {
tokens: [ tokens: [
{ {
__typename: "ServiceAccountToken", __typename: "ServiceAccountToken",
authToken: "**** AK05", authToken: "AK05",
id: "t1", id: "t1",
name: "default" name: "default"
} }

View file

@ -4,6 +4,14 @@ import { TypedMutation } from "../mutations";
import { serviceDetailsFragment, serviceFragment } from "./queries"; import { serviceDetailsFragment, serviceFragment } from "./queries";
import { ServiceCreate, ServiceCreateVariables } from "./types/ServiceCreate"; import { ServiceCreate, ServiceCreateVariables } from "./types/ServiceCreate";
import { ServiceDelete, ServiceDeleteVariables } from "./types/ServiceDelete"; import { ServiceDelete, ServiceDeleteVariables } from "./types/ServiceDelete";
import {
ServiceTokenCreate,
ServiceTokenCreateVariables
} from "./types/ServiceTokenCreate";
import {
ServiceTokenDelete,
ServiceTokenDeleteVariables
} from "./types/ServiceTokenDelete";
import { ServiceUpdate, ServiceUpdateVariables } from "./types/ServiceUpdate"; import { ServiceUpdate, ServiceUpdateVariables } from "./types/ServiceUpdate";
const serviceCreateMutation = gql` const serviceCreateMutation = gql`
@ -60,3 +68,34 @@ export const ServiceUpdateMutation = TypedMutation<
ServiceUpdate, ServiceUpdate,
ServiceUpdateVariables ServiceUpdateVariables
>(serviceUpdateMutation); >(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);

View file

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

View file

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

View file

@ -8,11 +8,16 @@ import useShop from "@saleor/hooks/useShop";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { getMutationState, maybe } from "@saleor/misc"; import { getMutationState, maybe } from "@saleor/misc";
import ServiceDeleteDialog from "@saleor/services/components/ServiceDeleteDialog"; import ServiceDeleteDialog from "@saleor/services/components/ServiceDeleteDialog";
import ServiceTokenCreateDialog from "@saleor/services/components/ServiceTokenCreateDialog";
import ServiceTokenDeleteDialog from "@saleor/services/components/ServiceTokenDeleteDialog";
import { import {
ServiceDeleteMutation, ServiceDeleteMutation,
ServiceTokenCreateMutation,
ServiceTokenDeleteMutation,
ServiceUpdateMutation ServiceUpdateMutation
} from "@saleor/services/mutations"; } from "@saleor/services/mutations";
import { ServiceDelete } from "@saleor/services/types/ServiceDelete"; import { ServiceDelete } from "@saleor/services/types/ServiceDelete";
import { ServiceTokenDelete } from "@saleor/services/types/ServiceTokenDelete";
import { ServiceUpdate } from "@saleor/services/types/ServiceUpdate"; import { ServiceUpdate } from "@saleor/services/types/ServiceUpdate";
import ServiceDetailsPage, { import ServiceDetailsPage, {
ServiceDetailsPageFormData ServiceDetailsPageFormData
@ -59,7 +64,7 @@ export const ServiceDetails: React.StatelessComponent<OrderListProps> = ({
); );
const onServiceUpdate = (data: ServiceUpdate) => { const onServiceUpdate = (data: ServiceUpdate) => {
if (!maybe(() => data.serviceAccountUpdate.errors.length !== 0)) { if (maybe(() => data.serviceAccountUpdate.errors.length === 0)) {
notify({ notify({
text: intl.formatMessage(commonMessages.savedChanges) text: intl.formatMessage(commonMessages.savedChanges)
}); });
@ -82,79 +87,175 @@ export const ServiceDetails: React.StatelessComponent<OrderListProps> = ({
variables={{ id }} variables={{ id }}
require={["serviceAccount"]} require={["serviceAccount"]}
> >
{({ data, loading }) => ( {({ data, loading, refetch }) => {
<ServiceUpdateMutation onCompleted={onServiceUpdate}> const onTokenDelete = (data: ServiceTokenDelete) => {
{(updateService, updateServiceOpts) => ( if (maybe(() => data.serviceAccountTokenDelete.errors.length === 0)) {
<ServiceDeleteMutation onCompleted={onServiceDelete}> notify({
{(deleteService, deleteServiceOpts) => { text: intl.formatMessage(commonMessages.savedChanges)
const handleSubmit = (data: ServiceDetailsPageFormData) => });
updateService({ refetch();
variables: { closeModal();
id, }
input: { };
isActive: data.isActive, return (
name: data.name, <ServiceUpdateMutation onCompleted={onServiceUpdate}>
permissions: data.hasFullAccess {(updateService, updateServiceOpts) => (
? shop.permissions.map(permission => permission.code) <ServiceDeleteMutation onCompleted={onServiceDelete}>
: data.permissions {(deleteService, deleteServiceOpts) => (
} <ServiceTokenCreateMutation>
} {(createToken, createTokenOpts) => (
}); <ServiceTokenDeleteMutation onCompleted={onTokenDelete}>
{(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 = () => const handleRemoveConfirm = () =>
deleteService({ deleteService({
variables: { variables: {
id id
} }
}); });
const formTransitionState = getMutationState( const handleTokenCreate = (name: string) =>
updateServiceOpts.called, createToken({
updateServiceOpts.loading, variables: {
maybe( input: {
() => updateServiceOpts.data.serviceAccountUpdate.errors name,
) serviceAccount: id
); }
}
});
const deleteTransitionState = getMutationState( const handleTokenDelete = () =>
deleteServiceOpts.called, deleteToken({
deleteServiceOpts.loading, variables: {
maybe( id: params.id
() => deleteServiceOpts.data.serviceAccountDelete.errors }
) });
);
return ( const formTransitionState = getMutationState(
<> updateServiceOpts.called,
<WindowTitle updateServiceOpts.loading,
title={maybe(() => data.serviceAccount.name)} maybe(
/> () =>
<ServiceDetailsPage updateServiceOpts.data.serviceAccountUpdate
disabled={loading} .errors
errors={[]} )
onBack={handleBack} );
onDelete={() => openModal("remove")}
onSubmit={handleSubmit} const deleteTransitionState = getMutationState(
onTokenCreate={() => openModal("create-token")} deleteServiceOpts.called,
onTokenDelete={() => openModal("remove-token")} deleteServiceOpts.loading,
permissions={maybe(() => shop.permissions)} maybe(
service={maybe(() => data.serviceAccount)} () =>
saveButtonBarState={formTransitionState} deleteServiceOpts.data.serviceAccountDelete
/> .errors
<ServiceDeleteDialog )
confirmButtonState={deleteTransitionState} );
name={maybe(() => data.serviceAccount.name, "...")}
onClose={closeModal} const createTokenTransitionState = getMutationState(
onConfirm={handleRemoveConfirm} createTokenOpts.called,
open={params.action === "remove"} createTokenOpts.loading,
/> maybe(
</> () =>
); createTokenOpts.data.serviceAccountTokenCreate
}} .errors
</ServiceDeleteMutation> )
)} );
</ServiceUpdateMutation>
)} const deleteTokenTransitionState = getMutationState(
deleteTokenOpts.called,
deleteTokenOpts.loading,
maybe(
() =>
deleteTokenOpts.data.serviceAccountTokenDelete
.errors
)
);
return (
<>
<WindowTitle
title={maybe(() => data.serviceAccount.name)}
/>
<ServiceDetailsPage
disabled={loading}
errors={[]}
onBack={handleBack}
onDelete={() => openModal("remove")}
onSubmit={handleSubmit}
onTokenCreate={() => openModal("create-token")}
onTokenDelete={id =>
openModal("remove-token", id)
}
permissions={maybe(() => shop.permissions)}
service={maybe(() => data.serviceAccount)}
saveButtonBarState={formTransitionState}
/>
<ServiceDeleteDialog
confirmButtonState={deleteTransitionState}
name={maybe(
() => data.serviceAccount.name,
"..."
)}
onClose={closeModal}
onConfirm={handleRemoveConfirm}
open={params.action === "remove"}
/>
<ServiceTokenCreateDialog
confirmButtonState={createTokenTransitionState}
onClose={closeModal}
onCreate={handleTokenCreate}
open={params.action === "create-token"}
token={maybe(
() =>
createTokenOpts.data
.serviceAccountTokenCreate.authToken
)}
/>
<ServiceTokenDeleteDialog
confirmButtonState={deleteTokenTransitionState}
name={maybe(() => {
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"}
/>
</>
);
}}
</ServiceTokenDeleteMutation>
)}
</ServiceTokenCreateMutation>
)}
</ServiceDeleteMutation>
)}
</ServiceUpdateMutation>
);
}}
</ServiceDetailsQuery> </ServiceDetailsQuery>
); );
}; };

View file

@ -13,7 +13,6 @@ import SaveFilterTabDialog, {
SaveFilterTabDialogFormData SaveFilterTabDialogFormData
} from "@saleor/components/SaveFilterTabDialog"; } from "@saleor/components/SaveFilterTabDialog";
import { configurationMenuUrl } from "@saleor/configuration"; import { configurationMenuUrl } from "@saleor/configuration";
import useShop from "@saleor/hooks/useShop";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { getMutationState, maybe } from "@saleor/misc"; import { getMutationState, maybe } from "@saleor/misc";
import { ServiceDeleteMutation } from "@saleor/services/mutations"; import { ServiceDeleteMutation } from "@saleor/services/mutations";

View file

@ -661,6 +661,11 @@ export interface ServiceAccountInput {
permissions?: (PermissionEnum | null)[] | null; permissions?: (PermissionEnum | null)[] | null;
} }
export interface ServiceAccountTokenInput {
name?: string | null;
serviceAccount: string;
}
export interface ShippingPriceInput { export interface ShippingPriceInput {
name?: string | null; name?: string | null;
price?: any | null; price?: any | null;