diff --git a/src/services/components/ServiceDetailsPage/ServiceDetailsPage.stories.tsx b/src/services/components/ServiceDetailsPage/ServiceDetailsPage.stories.tsx new file mode 100644 index 000000000..19d537f47 --- /dev/null +++ b/src/services/components/ServiceDetailsPage/ServiceDetailsPage.stories.tsx @@ -0,0 +1,35 @@ +import { storiesOf } from "@storybook/react"; +import React from "react"; + +import { permissions } from "@saleor/fixtures"; +import Decorator from "@saleor/storybook/Decorator"; +import { formError } from "@saleor/storybook/misc"; +import { service } from "../../fixtures"; +import ServiceDetailsPage, { + ServiceDetailsPageProps +} from "./ServiceDetailsPage"; + +const props: ServiceDetailsPageProps = { + disabled: false, + errors: [], + onBack: () => undefined, + onDelete: () => undefined, + onSubmit: () => undefined, + onTokenCreate: () => undefined, + onTokenDelete: () => undefined, + permissions, + saveButtonBarState: "default", + service +}; +storiesOf("Views / Services / Service details", module) + .addDecorator(Decorator) + .add("default", () => ) + .add("loading", () => ( + + )) + .add("form errors", () => ( + formError(field))} + /> + )); diff --git a/src/services/components/ServiceDetailsPage/ServiceDetailsPage.tsx b/src/services/components/ServiceDetailsPage/ServiceDetailsPage.tsx new file mode 100644 index 000000000..c1354f449 --- /dev/null +++ b/src/services/components/ServiceDetailsPage/ServiceDetailsPage.tsx @@ -0,0 +1,123 @@ +import React from "react"; +import { useIntl } from "react-intl"; + +import AccountPermissions from "@saleor/components/AccountPermissions"; +import AccountStatus from "@saleor/components/AccountStatus"; +import AppHeader from "@saleor/components/AppHeader"; +import CardSpacer from "@saleor/components/CardSpacer"; +import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton"; +import Container from "@saleor/components/Container"; +import Form from "@saleor/components/Form"; +import Grid from "@saleor/components/Grid"; +import PageHeader from "@saleor/components/PageHeader"; +import SaveButtonBar from "@saleor/components/SaveButtonBar"; +import { ShopInfo_shop_permissions } from "@saleor/components/Shop/types/ShopInfo"; +import { sectionNames } from "@saleor/intl"; +import { maybe } from "@saleor/misc"; +import { ServiceDetails_serviceAccount } from "@saleor/services/types/ServiceDetails"; +import { UserError } from "@saleor/types"; +import { PermissionEnum } from "@saleor/types/globalTypes"; +import ServiceInfo from "../ServiceInfo"; +import ServiceTokens from "../ServiceTokens"; + +export interface ServiceDetailsPageFormData { + hasFullAccess: boolean; + isActive: boolean; + name: string; + permissions: PermissionEnum[]; +} +export interface ServiceDetailsPageProps { + disabled: boolean; + errors: UserError[]; + permissions: ShopInfo_shop_permissions[]; + saveButtonBarState: ConfirmButtonTransitionState; + service: ServiceDetails_serviceAccount; + onBack: () => void; + onTokenDelete: (id: string) => void; + onDelete: () => void; + onTokenCreate: () => void; + onSubmit: (data: ServiceDetailsPageFormData) => void; +} + +const ServiceDetailsPage: React.FC = props => { + const { + disabled, + errors: formErrors, + permissions, + saveButtonBarState, + service, + onBack, + onDelete, + onTokenCreate, + onTokenDelete, + onSubmit + } = props; + const intl = useIntl(); + + const initialForm: ServiceDetailsPageFormData = { + hasFullAccess: maybe( + () => + permissions.filter( + perm => + maybe(() => service.permissions, []).filter( + userPerm => userPerm.code === perm.code + ).length === 0 + ).length === 0, + false + ), + isActive: maybe(() => service.isActive, false), + name: maybe(() => service.name, ""), + permissions: maybe(() => service.permissions, []).map(perm => perm.code) + }; + return ( + + {({ data, change, errors, hasChanged, submit }) => ( + + + {intl.formatMessage(sectionNames.serviceAccounts)} + + service.name)} /> + + + + + service.tokens)} + onCreate={onTokenCreate} + onDelete={onTokenDelete} + /> + + + + + + + + )} + + ); +}; + +ServiceDetailsPage.displayName = "ServiceDetailsPage"; +export default ServiceDetailsPage; diff --git a/src/services/components/ServiceDetailsPage/index.ts b/src/services/components/ServiceDetailsPage/index.ts new file mode 100644 index 000000000..9d3e06e0b --- /dev/null +++ b/src/services/components/ServiceDetailsPage/index.ts @@ -0,0 +1,2 @@ +export { default } from "./ServiceDetailsPage"; +export * from "./ServiceDetailsPage"; diff --git a/src/services/components/ServiceInfo/ServiceInfo.tsx b/src/services/components/ServiceInfo/ServiceInfo.tsx new file mode 100644 index 000000000..88ad3ee51 --- /dev/null +++ b/src/services/components/ServiceInfo/ServiceInfo.tsx @@ -0,0 +1,52 @@ +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import TextField from "@material-ui/core/TextField"; +import React from "react"; +import { useIntl } from "react-intl"; + +import CardTitle from "@saleor/components/CardTitle"; +import { FormChange } from "@saleor/hooks/useForm"; +import { FormErrors } from "@saleor/types"; + +export interface ServiceInfoProps { + data: { + name: string; + }; + disabled: boolean; + errors: FormErrors<"name">; + onChange: FormChange; +} + +const ServiceInfo: React.FC = props => { + const { data, disabled, errors, onChange } = props; + const intl = useIntl(); + + return ( + + + + + + + ); +}; + +ServiceInfo.displayName = "ServiceInfo"; +export default ServiceInfo; diff --git a/src/services/components/ServiceInfo/index.ts b/src/services/components/ServiceInfo/index.ts new file mode 100644 index 000000000..2d3a86c07 --- /dev/null +++ b/src/services/components/ServiceInfo/index.ts @@ -0,0 +1,2 @@ +export { default } from "./ServiceInfo"; +export * from "./ServiceInfo"; diff --git a/src/services/components/ServiceTokens/ServiceTokens.tsx b/src/services/components/ServiceTokens/ServiceTokens.tsx new file mode 100644 index 000000000..5949e3b62 --- /dev/null +++ b/src/services/components/ServiceTokens/ServiceTokens.tsx @@ -0,0 +1,124 @@ +import Button from "@material-ui/core/Button"; +import Card from "@material-ui/core/Card"; +import IconButton from "@material-ui/core/IconButton"; +import { Theme } from "@material-ui/core/styles"; +import Table from "@material-ui/core/Table"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import TableHead from "@material-ui/core/TableHead"; +import TableRow from "@material-ui/core/TableRow"; +import DeleteIcon from "@material-ui/icons/Delete"; +import makeStyles from "@material-ui/styles/makeStyles"; +import React from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import CardTitle from "@saleor/components/CardTitle"; +import Skeleton from "@saleor/components/Skeleton"; +import { maybe, renderCollection } from "@saleor/misc"; +import { ServiceDetailsFragment_tokens } from "@saleor/services/types/ServiceDetailsFragment"; + +export interface ServiceTokensProps { + tokens: ServiceDetailsFragment_tokens[]; + onCreate: () => void; + onDelete: (id: string) => void; +} + +const useStyles = makeStyles((theme: Theme) => ({ + [theme.breakpoints.down("md")]: { + colNote: { + width: 200 + } + }, + colActions: { + textAlign: "right", + width: 100 + }, + colKey: { + width: 200 + }, + colNote: {}, + table: { + tableLayout: "fixed" + } +})); + +const numberOfColumns = 3; + +const ServiceTokens: React.FC = props => { + const { tokens, onCreate, onDelete } = props; + const classes = useStyles(props); + const intl = useIntl(); + + return ( + + + + + } + /> + + + + + + + + + + + + + + + + {renderCollection( + tokens, + token => ( + + + {maybe(() => token.name, )} + + + {maybe(() => token.authToken, )} + + + onDelete(token.id)} + > + + + + + ), + () => ( + + + + + + ) + )} + + + + ); +}; + +ServiceTokens.displayName = "ServiceTokens"; +export default ServiceTokens; diff --git a/src/services/components/ServiceTokens/index.ts b/src/services/components/ServiceTokens/index.ts new file mode 100644 index 000000000..4ca5caf39 --- /dev/null +++ b/src/services/components/ServiceTokens/index.ts @@ -0,0 +1,2 @@ +export { default } from "./ServiceTokens"; +export * from "./ServiceTokens"; diff --git a/src/services/fixtures.ts b/src/services/fixtures.ts index a947e8b1f..e3d0521b4 100644 --- a/src/services/fixtures.ts +++ b/src/services/fixtures.ts @@ -1,3 +1,5 @@ +import { PermissionEnum } from "@saleor/types/globalTypes"; +import { ServiceDetails_serviceAccount } from "./types/ServiceDetails"; import { ServiceList_serviceAccounts_edges_node } from "./types/ServiceList"; export const serviceList: ServiceList_serviceAccounts_edges_node[] = [ @@ -20,3 +22,25 @@ export const serviceList: ServiceList_serviceAccounts_edges_node[] = [ name: "Magento Importer" } ]; + +export const service: ServiceDetails_serviceAccount = { + __typename: "ServiceAccount" as "ServiceAccount", + id: "1", + isActive: true, + name: "Magento Importer", + permissions: [ + { + __typename: "PermissionDisplay" as "PermissionDisplay", + code: PermissionEnum.MANAGE_PRODUCTS, + name: "Manage products." + } + ], + tokens: [ + { + __typename: "ServiceAccountToken", + authToken: "**** AK05", + id: "t1", + name: "default" + } + ] +}; diff --git a/src/services/queries.ts b/src/services/queries.ts index 1cc921335..a1be77734 100644 --- a/src/services/queries.ts +++ b/src/services/queries.ts @@ -1,6 +1,10 @@ import gql from "graphql-tag"; import { pageInfoFragment, TypedQuery } from "../queries"; +import { + ServiceDetails, + ServiceDetailsVariables +} from "./types/ServiceDetails"; import { ServiceList, ServiceListVariables } from "./types/ServiceList"; export const serviceFragment = gql` @@ -39,3 +43,32 @@ const serviceList = gql` export const ServiceListQuery = TypedQuery( serviceList ); + +const serviceDetailsFragment = gql` + ${serviceFragment} + fragment ServiceDetailsFragment on ServiceAccount { + ...ServiceFragment + permissions { + code + name + } + tokens { + id + name + authToken + } + } +`; + +const serviceDetails = gql` + ${serviceDetailsFragment} + query ServiceDetails($id: ID!) { + serviceAccount(id: $id) { + ...ServiceDetailsFragment + } + } +`; +export const ServiceDetailsFragmentQuery = TypedQuery< + ServiceDetails, + ServiceDetailsVariables +>(serviceDetails); diff --git a/src/services/types/ServiceDetails.ts b/src/services/types/ServiceDetails.ts new file mode 100644 index 000000000..1c150a98e --- /dev/null +++ b/src/services/types/ServiceDetails.ts @@ -0,0 +1,39 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { PermissionEnum } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL query operation: ServiceDetails +// ==================================================== + +export interface ServiceDetails_serviceAccount_permissions { + __typename: "PermissionDisplay"; + code: PermissionEnum; + name: string; +} + +export interface ServiceDetails_serviceAccount_tokens { + __typename: "ServiceAccountToken"; + id: string; + name: string | null; + authToken: string | null; +} + +export interface ServiceDetails_serviceAccount { + __typename: "ServiceAccount"; + id: string; + name: string | null; + isActive: boolean | null; + permissions: (ServiceDetails_serviceAccount_permissions | null)[] | null; + tokens: (ServiceDetails_serviceAccount_tokens | null)[] | null; +} + +export interface ServiceDetails { + serviceAccount: ServiceDetails_serviceAccount | null; +} + +export interface ServiceDetailsVariables { + id: string; +} diff --git a/src/services/types/ServiceDetailsFragment.ts b/src/services/types/ServiceDetailsFragment.ts new file mode 100644 index 000000000..e658335a2 --- /dev/null +++ b/src/services/types/ServiceDetailsFragment.ts @@ -0,0 +1,31 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { PermissionEnum } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL fragment: ServiceDetailsFragment +// ==================================================== + +export interface ServiceDetailsFragment_permissions { + __typename: "PermissionDisplay"; + code: PermissionEnum; + name: string; +} + +export interface ServiceDetailsFragment_tokens { + __typename: "ServiceAccountToken"; + id: string; + name: string | null; + authToken: string | null; +} + +export interface ServiceDetailsFragment { + __typename: "ServiceAccount"; + id: string; + name: string | null; + isActive: boolean | null; + permissions: (ServiceDetailsFragment_permissions | null)[] | null; + tokens: (ServiceDetailsFragment_tokens | null)[] | null; +} diff --git a/src/types/globalTypes.ts b/src/types/globalTypes.ts index 47b165de9..e4a258330 100644 --- a/src/types/globalTypes.ts +++ b/src/types/globalTypes.ts @@ -190,6 +190,7 @@ export enum PermissionEnum { MANAGE_STAFF = "MANAGE_STAFF", MANAGE_TRANSLATIONS = "MANAGE_TRANSLATIONS", MANAGE_USERS = "MANAGE_USERS", + MANAGE_WEBHOOKS = "MANAGE_WEBHOOKS", } export enum ProductOrderField {