diff --git a/package-lock.json b/package-lock.json index 8bf2228c5..cdfa829e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9409,7 +9409,8 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "bundled": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -9427,11 +9428,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -9444,15 +9447,18 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -9555,7 +9561,8 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "bundled": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -9565,6 +9572,7 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -9577,17 +9585,20 @@ "minimatch": { "version": "3.0.4", "bundled": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "bundled": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -9604,6 +9615,7 @@ "mkdirp": { "version": "0.5.1", "bundled": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -9676,7 +9688,8 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -9686,6 +9699,7 @@ "once": { "version": "1.4.0", "bundled": true, + "optional": true, "requires": { "wrappy": "1" } @@ -9761,7 +9775,8 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true + "bundled": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -9791,6 +9806,7 @@ "string-width": { "version": "1.0.2", "bundled": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -9808,6 +9824,7 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -9846,11 +9863,13 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true + "bundled": true, + "optional": true } } }, diff --git a/src/configuration/index.tsx b/src/configuration/index.tsx index 1c991472a..97ab04c36 100644 --- a/src/configuration/index.tsx +++ b/src/configuration/index.tsx @@ -7,6 +7,7 @@ import useNavigator from "@saleor/hooks/useNavigator"; import useUser from "@saleor/hooks/useUser"; import Navigation from "@saleor/icons/Navigation"; import Pages from "@saleor/icons/Pages"; +import Plugins from "@saleor/icons/Plugins"; import ProductTypes from "@saleor/icons/ProductTypes"; import ShippingMethods from "@saleor/icons/ShippingMethods"; import SiteSettings from "@saleor/icons/SiteSettings"; @@ -16,6 +17,7 @@ import { sectionNames } from "@saleor/intl"; import { maybe } from "@saleor/misc"; 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 { shippingZonesListUrl } from "@saleor/shipping/urls"; import { siteSettingsUrl } from "@saleor/siteSettings/urls"; @@ -24,6 +26,7 @@ import { taxSection } from "@saleor/taxes/urls"; import { PermissionEnum } from "@saleor/types/globalTypes"; import ConfigurationPage, { MenuItem } from "./ConfigurationPage"; +<<<<<<< HEAD export function createConfigurationMenu(intl: IntlShape): MenuItem[] { return [ { @@ -108,6 +111,79 @@ export function createConfigurationMenu(intl: IntlShape): MenuItem[] { } ]; } +======= +export const configurationMenu: MenuItem[] = [ + { + description: i18n.t("Determine attributes used to create product types"), + icon: , + permission: PermissionEnum.MANAGE_PRODUCTS, + title: i18n.t("Attributes"), + url: attributeListUrl() + }, + { + description: i18n.t("Define types of products you sell"), + icon: , + permission: PermissionEnum.MANAGE_PRODUCTS, + title: i18n.t("Product Types"), + url: productTypeListUrl() + }, + { + description: i18n.t("Manage your employees and their permissions"), + icon: , + permission: PermissionEnum.MANAGE_STAFF, + title: i18n.t("Staff Members"), + url: staffListUrl() + }, + { + description: i18n.t("Manage how you ship out orders."), + icon: , + permission: PermissionEnum.MANAGE_SHIPPING, + title: i18n.t("Shipping Methods"), + url: shippingZonesListUrl() + }, + { + description: i18n.t("Manage how your store charges tax"), + icon: , + permission: PermissionEnum.MANAGE_PRODUCTS, + title: i18n.t("Taxes"), + url: taxSection + }, + { + description: i18n.t("Define how users can navigate through your store"), + icon: , + permission: PermissionEnum.MANAGE_MENUS, + title: i18n.t("Navigation"), + url: menuListUrl() + }, + { + description: i18n.t("View and update your site settings"), + icon: , + permission: PermissionEnum.MANAGE_SETTINGS, + title: i18n.t("Site Settings"), + url: siteSettingsUrl() + }, + { + description: i18n.t("Manage and add additional pages"), + icon: , + permission: PermissionEnum.MANAGE_PAGES, + title: i18n.t("Pages"), + url: pageListUrl() + }, + { + description: i18n.t("View and update your plugins and their settings."), + icon: ( + + ), + permission: PermissionEnum.MANAGE_SETTINGS, + title: i18n.t("Plugins"), + url: pluginsListUrl() + } +]; +>>>>>>> Add plugin page, plugin list view export const configurationMenuUrl = "/configuration/"; diff --git a/src/icons/Plugins.tsx b/src/icons/Plugins.tsx new file mode 100644 index 000000000..4f67c5b5a --- /dev/null +++ b/src/icons/Plugins.tsx @@ -0,0 +1,17 @@ +import createSvgIcon from "@material-ui/icons/utils/createSvgIcon"; +import React from "react"; + +export const Plugins = createSvgIcon( + <> + + + + > +); +Plugins.displayName = "Plugins"; +export default Plugins; diff --git a/src/index.tsx b/src/index.tsx index 48e1bb36a..02ec5af46 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -39,6 +39,7 @@ import { navigationSection } from "./navigation/urls"; import { NotFound } from "./NotFound"; import OrdersSection from "./orders"; import PageSection from "./pages"; +import PluginsSection from "./plugins"; import ProductSection from "./products"; import ProductTypesSection from "./productTypes"; import ShippingSection from "./shipping"; @@ -171,6 +172,11 @@ const Routes: React.FC = () => { path="/pages" component={PageSection} /> + + createStyles({ + [theme.breakpoints.up("lg")]: { + colActive: {}, + colName: {}, + colAction: { + textAlign: "right", + "& svg": { + color: theme.palette.primary.main + } + } + }, + colName: {}, + colActive: {}, + colAction: {}, + link: { + cursor: "pointer" + } + }); + +const numberOfColumns = 4; + +const PluginList = withStyles(styles, { name: "PageList" })( + ({ + classes, + settings, + plugins, + disabled, + onNextPage, + pageInfo, + onRowClick, + onUpdateListSettings, + onPreviousPage, + isChecked, + selected, + toggle, + toggleAll, + toolbar + }: PluginListProps & WithStyles) => { + console.log(plugins); + return ( + + + + + {i18n.t("Name", { context: "table header" })} + + + {i18n.t("Active", { context: "table header" })} + + + {i18n.t("Action", { context: "table header" })} + + + + + + + + + {renderCollection( + plugins, + plugin => { + const isSelected = plugin ? isChecked(plugin.id) : false; + + return ( + + + toggle(plugin.id)} + /> + + + {maybe(() => plugin.name, )} + + + {maybe( + () => ( + + ), + + )} + + + + + + + + ); + }, + () => ( + + + {i18n.t("No plugins found")} + + + ) + )} + + + + ); + } +); +PluginList.displayName = "PluginList"; +export default PluginList; diff --git a/src/plugins/components/PluginsList/index.ts b/src/plugins/components/PluginsList/index.ts new file mode 100644 index 000000000..f8d1b14d3 --- /dev/null +++ b/src/plugins/components/PluginsList/index.ts @@ -0,0 +1,2 @@ +export { default } from "./PluginsList"; +export * from "./PluginsList"; diff --git a/src/plugins/components/PluginsListPage/PluginsListPage.tsx b/src/plugins/components/PluginsListPage/PluginsListPage.tsx new file mode 100644 index 000000000..5375117ae --- /dev/null +++ b/src/plugins/components/PluginsListPage/PluginsListPage.tsx @@ -0,0 +1,55 @@ +import Button from "@material-ui/core/Button"; +import AddIcon from "@material-ui/icons/Add"; +import React from "react"; + +import AppHeader from "@saleor/components/AppHeader"; +import Container from "@saleor/components/Container"; +import PageHeader from "@saleor/components/PageHeader"; +import i18n from "@saleor/i18n"; +import { ListActions, PageListProps } from "@saleor/types"; +import { PageList_pages_edges_node } from "../../types/PageList"; +import PluginsList from "../PluginsList/PluginsList"; + +export interface PluginsListPageProps extends PageListProps, ListActions { + pages: PageList_pages_edges_node[]; + onBack: () => void; +} + +const PluginsListPage: React.StatelessComponent = ({ + disabled, + settings, + onBack, + onNextPage, + onPreviousPage, + onRowClick, + onUpdateListSettings, + pageInfo, + plugins, + isChecked, + selected, + toggle, + toggleAll, + toolbar +}) => ( + + {i18n.t("Configuration")} + + + +); +PluginsListPage.displayName = "PluginsListPage"; +export default PluginsListPage; diff --git a/src/plugins/components/PluginsListPage/index.ts b/src/plugins/components/PluginsListPage/index.ts new file mode 100644 index 000000000..37ed6f973 --- /dev/null +++ b/src/plugins/components/PluginsListPage/index.ts @@ -0,0 +1,2 @@ +export { default } from "./PluginsListPage"; +export * from "./PluginsListPage"; diff --git a/src/plugins/index.tsx b/src/plugins/index.tsx new file mode 100644 index 000000000..651efd012 --- /dev/null +++ b/src/plugins/index.tsx @@ -0,0 +1,47 @@ +import { parse as parseQs } from "qs"; +import React from "react"; +import { Route, RouteComponentProps, Switch } from "react-router-dom"; + +import { WindowTitle } from "../components/WindowTitle"; +import i18n from "../i18n"; +import { + pluginsPath, + pluginsListPath, + PluginsListUrlQueryParams +} from "./urls"; +import PluginsDetailsComponent from "./views/PluginsDetails"; +import PluginsListComponent from "./views/PluginsList"; + +const PluginList: React.StatelessComponent> = ({ + location +}) => { + const qs = parseQs(location.search.substr(1)); + const params: PluginsListUrlQueryParams = qs; + return ; +}; + +// const PageDetails: React.StatelessComponent> = ({ +// match +// }) => { +// const qs = parseQs(location.search.substr(1)); +// const params: PluginsListUrlQueryParams = qs; + +// return ( +// +// ); +// }; + +const Component = () => ( + <> + + + + {/* */} + + > +); + +export default Component; diff --git a/src/plugins/mutations.ts b/src/plugins/mutations.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/plugins/queries.ts b/src/plugins/queries.ts new file mode 100644 index 000000000..dfdf1cd69 --- /dev/null +++ b/src/plugins/queries.ts @@ -0,0 +1,68 @@ +import gql from "graphql-tag"; + +import { TypedQuery } from "../queries"; +import { PluginDetails, PluginDetailsVariables } from "./types/PluginDetails"; +import { PluginsList, PluginsListVariables } from "./types/PluginsList"; + +export const pluginsFragment = gql` + fragment pluginFragment on PluginConfiguration { + id + name + description + active + } +`; + +// export const pluginsDetailsFragment = gql` +// ${pluginsFragment} +// fragment pluginsDetailsFragment on PluginConfiguration { +// ...pluginFragment +// configuration { +// name +// type +// value +// helpText +// label +// } +// } +// `; + +const pluginsList = gql` + ${pluginsFragment} + query pluginConfigurations( + $first: Int + $after: String + $last: Int + $before: String + ) { + pluginConfigurations( + before: $before + after: $after + first: $first + last: $last + ) { + edges { + node { + ...pluginFragment + } + } + } + } +`; +export const TypedPluginsListQuery = TypedQuery< + PluginsList, + PluginsListVariables +>(pluginsList); + +// const pluginsDetails = gql` +// ${pluginsDetailsFragment} +// query pluginConfiguration($id: ID!) { +// page(id: $id) { +// ...pluginsDetailsFragment +// } +// } +// `; +// export const TypedPluginsDetailsQuery = TypedQuery< +// PluginDetails, +// PluginDetailsVariables +// >(pluginsDetails); diff --git a/src/plugins/types/pluginConfigurations.ts b/src/plugins/types/pluginConfigurations.ts new file mode 100644 index 000000000..3204a58d7 --- /dev/null +++ b/src/plugins/types/pluginConfigurations.ts @@ -0,0 +1,36 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL query operation: pluginConfigurations +// ==================================================== + +export interface pluginConfigurations_pluginConfigurations_edges_node { + __typename: "PluginConfiguration"; + id: string; + name: string; + description: string; + active: boolean; +} + +export interface pluginConfigurations_pluginConfigurations_edges { + __typename: "PluginConfigurationCountableEdge"; + node: pluginConfigurations_pluginConfigurations_edges_node; +} + +export interface pluginConfigurations_pluginConfigurations { + __typename: "PluginConfigurationCountableConnection"; + edges: pluginConfigurations_pluginConfigurations_edges[]; +} + +export interface pluginConfigurations { + pluginConfigurations: pluginConfigurations_pluginConfigurations | null; +} + +export interface pluginConfigurationsVariables { + first?: number | null; + after?: string | null; + last?: number | null; + before?: string | null; +} diff --git a/src/plugins/types/pluginFragment.ts b/src/plugins/types/pluginFragment.ts new file mode 100644 index 000000000..a1e6f5f6c --- /dev/null +++ b/src/plugins/types/pluginFragment.ts @@ -0,0 +1,15 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL fragment: pluginFragment +// ==================================================== + +export interface pluginFragment { + __typename: "PluginConfiguration"; + id: string; + name: string; + description: string; + active: boolean; +} diff --git a/src/plugins/urls.ts b/src/plugins/urls.ts new file mode 100644 index 000000000..c2bb025e7 --- /dev/null +++ b/src/plugins/urls.ts @@ -0,0 +1,21 @@ +import { stringify as stringifyQs } from "qs"; +import urlJoin from "url-join"; + +import { BulkAction, Dialog, Pagination, SingleAction } from "../types"; + +export const pluginsSection = "/plugins/"; + +export const pluginsListPath = pluginsSection; +export type PluginsListUrlDialog = "remove" | "remove-many"; +export type PluginsListUrlQueryParams = BulkAction & + Dialog & + Pagination & + SingleAction; +export const pluginsListUrl = (params?: PluginsListUrlQueryParams) => + pluginsListPath + "?" + stringifyQs(params); + +export const pluginsPath = (id: string) => urlJoin(pluginsSection, id); +export type PluginsUrlDialog = "edit-item" | "remove"; +export type PluginsUrlQueryParams = Dialog & SingleAction; +export const pluginsUrl = (id: string, params?: PluginsUrlQueryParams) => + pluginsPath(encodeURIComponent(id)) + "?" + stringifyQs(params); diff --git a/src/plugins/views/PluginsList.tsx b/src/plugins/views/PluginsList.tsx new file mode 100644 index 000000000..ef954a175 --- /dev/null +++ b/src/plugins/views/PluginsList.tsx @@ -0,0 +1,81 @@ +import Button from "@material-ui/core/Button"; +import DialogContentText from "@material-ui/core/DialogContentText"; +import IconButton from "@material-ui/core/IconButton"; +import DeleteIcon from "@material-ui/icons/Delete"; +import React from "react"; + +import ActionDialog from "@saleor/components/ActionDialog"; +import { configurationMenuUrl } from "@saleor/configuration"; +import useBulkActions from "@saleor/hooks/useBulkActions"; +import useListSettings from "@saleor/hooks/useListSettings"; +import useNavigator from "@saleor/hooks/useNavigator"; +import useNotifier from "@saleor/hooks/useNotifier"; +import usePaginator, { + createPaginationState +} from "@saleor/hooks/usePaginator"; +import i18n from "@saleor/i18n"; +import { getMutationState, maybe } from "@saleor/misc"; +import { ListViews } from "@saleor/types"; +import PluginsListPage from "../components/PluginsListPage/PluginsListPage"; +import { TypedPluginsListQuery } from "../queries"; +import { + pluginsListUrl, + PluginsListUrlDialog, + PluginsListUrlQueryParams, + pluginsUrl +} from "../urls"; + +interface PluginsListProps { + params: PluginsListUrlQueryParams; +} + +export const PluginsList: React.StatelessComponent = ({ + params +}) => { + const navigate = useNavigator(); + const notify = useNotifier(); + const paginate = usePaginator(); + const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions( + params.ids + ); + const { updateListSettings, settings } = useListSettings( + ListViews.PAGES_LIST + ); + const paginationState = createPaginationState(settings.rowNumber, params); + + return ( + + {({ data, loading, refetch }) => { + const { loadNextPage, loadPreviousPage, pageInfo } = paginate( + maybe(() => data.pages.pageInfo), + paginationState, + params + ); + + return ( + <> + + data.pluginConfigurations.edges.map(edge => edge.node) + )} + pageInfo={pageInfo} + onBack={() => navigate(configurationMenuUrl)} + onNextPage={loadNextPage} + onPreviousPage={loadPreviousPage} + onUpdateListSettings={updateListSettings} + onRowClick={id => () => navigate(pluginsUrl(id))} + isChecked={isSelected} + selected={listElements.length} + toggle={toggle} + toggleAll={toggleAll} + /> + > + ); + }} + + ); +}; + +export default PluginsList; diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 0b6f69afe..a34423093 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -26660,6 +26660,47 @@ exports[`Storyshots Views / Configuration default 1`] = ` + + + + + + + + + + + + Plugins + + + View and update your plugins and their settings. + + + +
+ Plugins +
+ View and update your plugins and their settings. +