diff --git a/src/configuration/index.tsx b/src/configuration/index.tsx index cb987e8cc..8f70c5297 100644 --- a/src/configuration/index.tsx +++ b/src/configuration/index.tsx @@ -6,6 +6,7 @@ import { WindowTitle } from "@saleor/components/WindowTitle"; import useNavigator from "@saleor/hooks/useNavigator"; import useUser from "@saleor/hooks/useUser"; import Attributes from "@saleor/icons/Attributes"; +import Bot from "@saleor/icons/Bot"; import Navigation from "@saleor/icons/Navigation"; import Pages from "@saleor/icons/Pages"; import Plugins from "@saleor/icons/Plugins"; @@ -20,6 +21,7 @@ import { menuListUrl } from "@saleor/navigation/urls"; import { pageListUrl } from "@saleor/pages/urls"; import { pluginsListUrl } from "@saleor/plugins/urls"; import { productTypeListUrl } from "@saleor/productTypes/urls"; +import { serviceListUrl } from "@saleor/services/urls"; import { shippingZonesListUrl } from "@saleor/shipping/urls"; import { siteSettingsUrl } from "@saleor/siteSettings/urls"; import { staffListUrl } from "@saleor/staff/urls"; @@ -150,6 +152,15 @@ export function createConfigurationMenu(intl: IntlShape): MenuSection[] { permission: PermissionEnum.MANAGE_SETTINGS, title: intl.formatMessage(sectionNames.plugins), url: pluginsListUrl() + }, + { + description: intl.formatMessage({ + defaultMessage: "Manage external integrations accounts" + }), + icon: , + permission: PermissionEnum.MANAGE_SERVICE_ACCOUNTS, + title: intl.formatMessage(sectionNames.serviceAccounts), + url: serviceListUrl() } ] } diff --git a/src/icons/Bot.tsx b/src/icons/Bot.tsx new file mode 100644 index 000000000..926c20fe0 --- /dev/null +++ b/src/icons/Bot.tsx @@ -0,0 +1,13 @@ +import createSvgIcon from "@material-ui/icons/utils/createSvgIcon"; +import React from "react"; + +export const Bot = createSvgIcon( + +); +Bot.displayName = "Bot"; +export default Bot; diff --git a/src/intl.ts b/src/intl.ts index 254e4ac44..cbe7327a6 100644 --- a/src/intl.ts +++ b/src/intl.ts @@ -171,6 +171,10 @@ export const sectionNames = defineMessages({ defaultMessage: "Sales", description: "sales section name" }, + serviceAccounts: { + defaultMessage: "Service Accounts", + description: "service accounts section name" + }, shipping: { defaultMessage: "Shipping Methods", description: "shipping section name" diff --git a/src/services/components/ServiceList/ServiceList.tsx b/src/services/components/ServiceList/ServiceList.tsx new file mode 100644 index 000000000..9debb1f83 --- /dev/null +++ b/src/services/components/ServiceList/ServiceList.tsx @@ -0,0 +1,180 @@ +import IconButton from "@material-ui/core/IconButton"; +import { + createStyles, + Theme, + WithStyles, + withStyles +} 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 TableFooter from "@material-ui/core/TableFooter"; +import TableRow from "@material-ui/core/TableRow"; +import Typography from "@material-ui/core/Typography"; +import DeleteIcon from "@material-ui/icons/Delete"; +import EditIcon from "@material-ui/icons/Edit"; +import React from "react"; +import { FormattedMessage } from "react-intl"; + +import Checkbox from "@saleor/components/Checkbox"; +import Skeleton from "@saleor/components/Skeleton"; +import TableHead from "@saleor/components/TableHead"; +import TablePagination from "@saleor/components/TablePagination"; +import { maybe, renderCollection } from "@saleor/misc"; +import { ListActions, ListProps } from "@saleor/types"; +import { ServiceList_serviceAccounts_edges_node } from "../../types/ServiceList"; + +export interface ServiceListProps extends ListProps, ListActions { + services: ServiceList_serviceAccounts_edges_node[]; + onRemove: (id: string) => void; +} + +const styles = (theme: Theme) => + createStyles({ + [theme.breakpoints.up("lg")]: { + colName: { + "&&": { + width: "auto" + } + } + }, + colAction: { + "&&": { + paddingRight: theme.spacing.unit + }, + textAlign: "right", + width: 100 + }, + colName: { + paddingLeft: 0, + width: 250 + }, + table: { + tableLayout: "fixed" + } + }); + +const numberOfColumns = 3; + +const ServiceList = withStyles(styles, { + name: "ServiceList" +})( + ({ + classes, + settings, + disabled, + onNextPage, + onPreviousPage, + onUpdateListSettings, + onRemove, + onRowClick, + pageInfo, + services, + isChecked, + selected, + toggle, + toggleAll, + toolbar + }: ServiceListProps & WithStyles) => ( + + + + + + + + + + + + + + {renderCollection( + services, + service => { + const isSelected = service ? isChecked(service.id) : false; + + return ( + + + toggle(service.id)} + /> + + + + {maybe(() => service.name, )} + + + {maybe(() => + service.isActive ? ( + + ) : ( + + ) + )} + + + + + + + onRemove(service.id) : undefined} + > + + + + + ); + }, + () => ( + + + + + + ) + )} + +
+ ) +); +ServiceList.displayName = "ServiceList"; +export default ServiceList; diff --git a/src/services/components/ServiceList/index.ts b/src/services/components/ServiceList/index.ts new file mode 100644 index 000000000..3cfb6cc3d --- /dev/null +++ b/src/services/components/ServiceList/index.ts @@ -0,0 +1,2 @@ +export { default } from "./ServiceList"; +export * from "./ServiceList"; diff --git a/src/services/components/ServiceListPage/ServiceListPage.stories.tsx b/src/services/components/ServiceListPage/ServiceListPage.stories.tsx new file mode 100644 index 000000000..cf670a2c7 --- /dev/null +++ b/src/services/components/ServiceListPage/ServiceListPage.stories.tsx @@ -0,0 +1,32 @@ +import { storiesOf } from "@storybook/react"; +import React from "react"; + +import { + listActionsProps, + pageListProps, + searchPageProps, + tabPageProps +} from "@saleor/fixtures"; +import ServiceListPage, { + ServiceListPageProps +} from "@saleor/services/components/ServiceListPage"; +import Decorator from "@saleor/storybook/Decorator"; +import { serviceList } from "../../fixtures"; + +const props: ServiceListPageProps = { + ...listActionsProps, + ...pageListProps.default, + ...searchPageProps, + ...tabPageProps, + onBack: () => undefined, + onRemove: () => undefined, + services: serviceList +}; + +storiesOf("Views / Services / Service list", module) + .addDecorator(Decorator) + .add("default", () => ) + .add("loading", () => ( + + )) + .add("no data", () => ); diff --git a/src/services/components/ServiceListPage/ServiceListPage.tsx b/src/services/components/ServiceListPage/ServiceListPage.tsx new file mode 100644 index 000000000..1a3394331 --- /dev/null +++ b/src/services/components/ServiceListPage/ServiceListPage.tsx @@ -0,0 +1,82 @@ +import Button from "@material-ui/core/Button"; +import Card from "@material-ui/core/Card"; +import React from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import AppHeader from "@saleor/components/AppHeader"; +import Container from "@saleor/components/Container"; +import PageHeader from "@saleor/components/PageHeader"; +import SearchBar from "@saleor/components/SearchBar"; +import { sectionNames } from "@saleor/intl"; +import { + ListActions, + PageListProps, + SearchPageProps, + TabPageProps +} from "@saleor/types"; +import { ServiceList_serviceAccounts_edges_node } from "../../types/ServiceList"; +import ServiceList from "../ServiceList"; + +export interface ServiceListPageProps + extends PageListProps, + ListActions, + SearchPageProps, + TabPageProps { + services: ServiceList_serviceAccounts_edges_node[]; + onBack: () => void; + onRemove: (id: string) => void; +} + +const ServiceListPage: React.StatelessComponent = ({ + currentTab, + initialSearch, + onAdd, + onAll, + onBack, + onSearchChange, + onTabChange, + onTabDelete, + onTabSave, + tabs, + ...listProps +}) => { + const intl = useIntl(); + + return ( + + + {intl.formatMessage(sectionNames.configuration)} + + + + + + + + + + ); +}; +ServiceListPage.displayName = "ServiceListPage"; +export default ServiceListPage; diff --git a/src/services/components/ServiceListPage/index.ts b/src/services/components/ServiceListPage/index.ts new file mode 100644 index 000000000..0cbe92960 --- /dev/null +++ b/src/services/components/ServiceListPage/index.ts @@ -0,0 +1,2 @@ +export { default } from "./ServiceListPage"; +export * from "./ServiceListPage"; diff --git a/src/services/fixtures.ts b/src/services/fixtures.ts new file mode 100644 index 000000000..a947e8b1f --- /dev/null +++ b/src/services/fixtures.ts @@ -0,0 +1,22 @@ +import { ServiceList_serviceAccounts_edges_node } from "./types/ServiceList"; + +export const serviceList: ServiceList_serviceAccounts_edges_node[] = [ + { + __typename: "ServiceAccount" as "ServiceAccount", + id: "1", + isActive: true, + name: "Slack" + }, + { + __typename: "ServiceAccount" as "ServiceAccount", + id: "2", + isActive: true, + name: "Facebook Market" + }, + { + __typename: "ServiceAccount" as "ServiceAccount", + id: "3", + isActive: false, + name: "Magento Importer" + } +]; diff --git a/src/services/index.tsx b/src/services/index.tsx new file mode 100644 index 000000000..cb7675187 --- /dev/null +++ b/src/services/index.tsx @@ -0,0 +1,51 @@ +import { parse as parseQs } from "qs"; +import React from "react"; +import { useIntl } from "react-intl"; +import { Route, RouteComponentProps, Switch } from "react-router-dom"; + +import { sectionNames } from "@saleor/intl"; +import { WindowTitle } from "../components/WindowTitle"; +import { + serviceListPath, + ServiceListUrlQueryParams + // servicePath +} from "./urls"; +// import ServiceDetailsComponent from "./views/ServiceDetails"; +import ServiceListComponent from "./views/ServiceList"; + +const PluginList: React.StatelessComponent> = ({ + location +}) => { + const qs = parseQs(location.search.substr(1)); + const params: ServiceListUrlQueryParams = qs; + return ; +}; + +// const ServiceDetails: React.StatelessComponent> = ({ +// match +// }) => { +// const qs = parseQs(location.search.substr(1)); +// const params: ServiceListUrlQueryParams = qs; + +// return ( +// +// ); +// }; + +const Component = () => { + const intl = useIntl(); + return ( + <> + + + + {/* */} + + + ); +}; + +export default Component; diff --git a/src/services/queries.ts b/src/services/queries.ts new file mode 100644 index 000000000..1cc921335 --- /dev/null +++ b/src/services/queries.ts @@ -0,0 +1,41 @@ +import gql from "graphql-tag"; + +import { pageInfoFragment, TypedQuery } from "../queries"; +import { ServiceList, ServiceListVariables } from "./types/ServiceList"; + +export const serviceFragment = gql` + fragment ServiceFragment on ServiceAccount { + id + name + isActive + } +`; + +const serviceList = gql` + ${pageInfoFragment} + ${serviceFragment} + query ServiceList( + $first: Int + $after: String + $last: Int + $before: String + $filter: ServiceAccountFilterInput + ) { + serviceAccounts( + first: $first + after: $after + before: $before + last: $last + filter: $filter + ) { + edges { + node { + ...ServiceFragment + } + } + } + } +`; +export const ServiceListQuery = TypedQuery( + serviceList +); diff --git a/src/services/types/ServiceFragment.ts b/src/services/types/ServiceFragment.ts new file mode 100644 index 000000000..5c0e96fec --- /dev/null +++ b/src/services/types/ServiceFragment.ts @@ -0,0 +1,14 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL fragment: ServiceFragment +// ==================================================== + +export interface ServiceFragment { + __typename: "ServiceAccount"; + id: string; + name: string | null; + isActive: boolean | null; +} diff --git a/src/services/types/ServiceList.ts b/src/services/types/ServiceList.ts new file mode 100644 index 000000000..d32a5b070 --- /dev/null +++ b/src/services/types/ServiceList.ts @@ -0,0 +1,38 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { ServiceAccountFilterInput } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL query operation: ServiceList +// ==================================================== + +export interface ServiceList_serviceAccounts_edges_node { + __typename: "ServiceAccount"; + id: string; + name: string | null; + isActive: boolean | null; +} + +export interface ServiceList_serviceAccounts_edges { + __typename: "ServiceAccountCountableEdge"; + node: ServiceList_serviceAccounts_edges_node; +} + +export interface ServiceList_serviceAccounts { + __typename: "ServiceAccountCountableConnection"; + edges: ServiceList_serviceAccounts_edges[]; +} + +export interface ServiceList { + serviceAccounts: ServiceList_serviceAccounts | null; +} + +export interface ServiceListVariables { + first?: number | null; + after?: string | null; + last?: number | null; + before?: string | null; + filter?: ServiceAccountFilterInput | null; +} diff --git a/src/services/urls.ts b/src/services/urls.ts new file mode 100644 index 000000000..0c78289ee --- /dev/null +++ b/src/services/urls.ts @@ -0,0 +1,37 @@ +import { stringify as stringifyQs } from "qs"; +import urlJoin from "url-join"; + +import { + ActiveTab, + BulkAction, + Dialog, + Filters, + Pagination, + SingleAction, + TabActionDialog +} from "../types"; + +export const serviceSection = "/services/"; + +export const serviceListPath = serviceSection; +export enum ServiceListUrlFiltersEnum { + query = "query" +} +export type ServiceListUrlFilters = Filters; +export type ServiceListUrlDialog = "remove" | TabActionDialog; +export type ServiceListUrlQueryParams = ActiveTab & + BulkAction & + ServiceListUrlFilters & + Dialog & + Pagination; +export const serviceListUrl = (params?: ServiceListUrlQueryParams) => + serviceListPath + "?" + stringifyQs(params); + +export const servicePath = (id: string) => urlJoin(serviceSection, id); +export type ServiceUrlDialog = "create-token" | "remove" | "remove-token"; +export type ServiceUrlQueryParams = Dialog & SingleAction; +export const serviceUrl = (id: string, params?: ServiceUrlQueryParams) => + servicePath(encodeURIComponent(id)) + "?" + stringifyQs(params); + +export const serviceAddPath = urlJoin(serviceSection, "add"); +export const serviceAddUrl = serviceAddPath; diff --git a/src/services/views/ServiceList/ServiceList.tsx b/src/services/views/ServiceList/ServiceList.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/src/services/views/ServiceList/index.ts b/src/services/views/ServiceList/index.ts new file mode 100644 index 000000000..3cfb6cc3d --- /dev/null +++ b/src/services/views/ServiceList/index.ts @@ -0,0 +1,2 @@ +export { default } from "./ServiceList"; +export * from "./ServiceList"; diff --git a/src/types/globalTypes.ts b/src/types/globalTypes.ts index deb47b433..47b165de9 100644 --- a/src/types/globalTypes.ts +++ b/src/types/globalTypes.ts @@ -649,6 +649,11 @@ export interface SeoInput { description?: string | null; } +export interface ServiceAccountFilterInput { + search?: string | null; + isActive?: boolean | null; +} + export interface ShippingPriceInput { name?: string | null; price?: any | null;