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;