diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f5bf9dbb..bd0bb9153 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,3 +10,4 @@ All notable, unreleased changes to this project will be documented in this file. - Add switch to make attribute available in product list as a column - #99 by @dominik-zeglen - Add tc tags for E2E testing - #134 by @dominik-zeglen - Use react-intl - #105 by @dominik-zeglen +- Add dynamic dashboard settings - #135 by @benekex2 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/schema.graphql b/schema.graphql index 5490d13e7..8b13a6034 100644 --- a/schema.graphql +++ b/schema.graphql @@ -143,8 +143,8 @@ type AssignNavigation { type Attribute implements Node { id: ID! - productTypes(before: String, after: String, first: Int, last: Int): ProductTypeCountableConnection - productVariantTypes(before: String, after: String, first: Int, last: Int): ProductTypeCountableConnection + productTypes(before: String, after: String, first: Int, last: Int): ProductTypeCountableConnection! + productVariantTypes(before: String, after: String, first: Int, last: Int): ProductTypeCountableConnection! privateMeta: [MetaStore]! meta: [MetaStore]! inputType: AttributeInputTypeEnum @@ -228,6 +228,7 @@ input AttributeFilterInput { filterableInDashboard: Boolean availableInGrid: Boolean search: String + ids: [ID] } input AttributeInput { @@ -518,7 +519,8 @@ type Checkout implements Node { shippingAddress: Address shippingMethod: ShippingMethod note: String! - discountAmount: Money + discountAmount: Money @deprecated(reason: "Use discount instead.") + discount: Money discountName: String translatedDiscountName: String voucherCode: String @@ -969,7 +971,6 @@ enum CountryCode { LT LU MO - MK MG MW MY @@ -1003,6 +1004,7 @@ enum CountryCode { NU NF KP + MK MP NO OM @@ -1867,6 +1869,7 @@ type Mutations { paymentCapture(amount: Decimal, paymentId: ID!): PaymentCapture paymentRefund(amount: Decimal, paymentId: ID!): PaymentRefund paymentVoid(paymentId: ID!): PaymentVoid + paymentSecureConfirm(paymentId: ID!): PaymentSecureConfirm pageCreate(input: PageInput!): PageCreate pageDelete(id: ID!): PageDelete pageBulkDelete(ids: [ID]!): PageBulkDelete @@ -1909,7 +1912,7 @@ type Mutations { giftCardCreate(input: GiftCardCreateInput!): GiftCardCreate giftCardDeactivate(id: ID!): GiftCardDeactivate giftCardUpdate(id: ID!, input: GiftCardUpdateInput!): GiftCardUpdate - pluginConfigurationUpdate(id: ID!, input: PluginConfigurationUpdateInput!): PluginConfigurationUpdate + pluginUpdate(id: ID!, input: PluginUpdateInput!): PluginUpdate saleCreate(input: SaleInput!): SaleCreate saleDelete(id: ID!): SaleDelete saleBulkDelete(ids: [ID]!): SaleBulkDelete @@ -1946,8 +1949,9 @@ type Mutations { checkoutClearMetadata(id: ID!, input: MetaPath!): CheckoutClearStoredMeta checkoutUpdatePrivateMetadata(id: ID!, input: MetaInput!): CheckoutUpdatePrivateMeta checkoutClearPrivateMetadata(id: ID!, input: MetaPath!): CheckoutClearStoredPrivateMeta - requestPasswordReset(email: String!): RequestPasswordReset - setPassword(id: ID!, input: SetPasswordInput!): SetPassword + requestPasswordReset(email: String!, redirectUrl: String!): RequestPasswordReset + setPassword(token: String!, email: String!, password: String!): SetPassword + passwordChange(newPassword: String!, oldPassword: String!): PasswordChange userUpdateMetadata(id: ID!, input: MetaInput!): UserUpdateMeta userClearStoredMetadata(id: ID!, input: MetaPath!): UserClearStoredMeta accountAddressCreate(input: AddressInput!, type: AddressTypeEnum): AccountAddressCreate @@ -1956,7 +1960,7 @@ type Mutations { accountSetDefaultAddress(id: ID!, type: AddressTypeEnum!): AccountSetDefaultAddress accountRegister(input: AccountRegisterInput!): AccountRegister accountUpdate(input: AccountInput!): AccountUpdate - accountRequestDeletion: AccountRequestDeletion + accountRequestDeletion(redirectUrl: String!): AccountRequestDeletion accountDelete(token: String!): AccountDelete customerPasswordReset(input: CustomerPasswordResetInput!): CustomerPasswordReset customerAddressCreate(input: AddressInput!, type: AddressTypeEnum): CustomerAddressCreate @@ -2011,12 +2015,12 @@ type Order implements Node { billingAddress: Address shippingAddress: Address shippingMethod: ShippingMethod - shippingPrice: TaxedMoney shippingMethodName: String + shippingPrice: TaxedMoney token: String! voucher: Voucher giftCards: [GiftCard] - discountAmount: Money + discount: Money discountName: String! translatedDiscountName: String! displayGrossPrices: Boolean! @@ -2041,6 +2045,7 @@ type Order implements Node { totalBalance: Money! userEmail: String isShippingRequired: Boolean! + discountAmount: Money! @deprecated(reason: "Deprecated: use discount instead.") } enum OrderAction { @@ -2341,6 +2346,11 @@ type PageUpdate { page: Page } +type PasswordChange { + errors: [Error!] + user: User +} + type PasswordReset { errors: [Error!] } @@ -2404,6 +2414,11 @@ type PaymentRefund { payment: Payment } +type PaymentSecureConfirm { + errors: [Error!] + payment: Payment +} + type PaymentSource { gateway: String! creditCardInfo: CreditCard @@ -2435,7 +2450,7 @@ enum PermissionEnum { MANAGE_TRANSLATIONS } -type PluginConfiguration implements Node { +type Plugin implements Node { name: String! description: String! active: Boolean! @@ -2443,23 +2458,23 @@ type PluginConfiguration implements Node { id: ID! } -type PluginConfigurationCountableConnection { +type PluginCountableConnection { pageInfo: PageInfo! - edges: [PluginConfigurationCountableEdge!]! + edges: [PluginCountableEdge!]! totalCount: Int } -type PluginConfigurationCountableEdge { - node: PluginConfiguration! +type PluginCountableEdge { + node: Plugin! cursor: String! } -type PluginConfigurationUpdate { +type PluginUpdate { errors: [Error!] - pluginConfiguration: PluginConfiguration + plugin: Plugin } -input PluginConfigurationUpdateInput { +input PluginUpdateInput { active: Boolean configuration: [ConfigurationItemInput] } @@ -2493,6 +2508,7 @@ type Product implements Node { isAvailable: Boolean basePrice: Money price: Money @deprecated(reason: "Has been replaced by 'basePrice'") + minimalVariantPrice: Money taxRate: TaxRateType @deprecated(reason: "taxRate is deprecated. Use taxType to obtain taxCode for given tax gateway") taxType: TaxType attributes: [SelectedAttribute!]! @@ -2579,6 +2595,7 @@ input ProductFilterInput { stockAvailability: StockAvailability productType: ID search: String + minimalPrice: PriceRangeInput } type ProductImage implements Node { @@ -2656,6 +2673,7 @@ input ProductOrder { enum ProductOrderField { NAME PRICE + MINIMAL_PRICE DATE } @@ -2748,6 +2766,7 @@ enum ProductTypeEnum { } input ProductTypeFilterInput { + search: String configurable: ProductTypeConfigurable productType: ProductTypeEnum } @@ -2925,7 +2944,7 @@ type Query { product(id: ID!): Product products(filter: ProductFilterInput, attributes: [AttributeScalar], categories: [ID], collections: [ID], priceLte: Float, priceGte: Float, sortBy: ProductOrder, stockAvailability: StockAvailability, query: String, before: String, after: String, first: Int, last: Int): ProductCountableConnection productType(id: ID!): ProductType - productTypes(filter: ProductTypeFilterInput, before: String, after: String, first: Int, last: Int): ProductTypeCountableConnection + productTypes(filter: ProductTypeFilterInput, query: String, before: String, after: String, first: Int, last: Int): ProductTypeCountableConnection productVariant(id: ID!): ProductVariant productVariants(ids: [ID], before: String, after: String, first: Int, last: Int): ProductVariantCountableConnection reportProductSales(period: ReportingPeriod!, before: String, after: String, first: Int, last: Int): ProductVariantCountableConnection @@ -2946,8 +2965,8 @@ type Query { menuItems(query: String, before: String, after: String, first: Int, last: Int): MenuItemCountableConnection giftCard(id: ID!): GiftCard giftCards(before: String, after: String, first: Int, last: Int): GiftCardCountableConnection - pluginConfiguration(id: ID!): PluginConfiguration - pluginConfigurations(before: String, after: String, first: Int, last: Int): PluginConfigurationCountableConnection + plugin(id: ID!): Plugin + plugins(before: String, after: String, first: Int, last: Int): PluginCountableConnection sale(id: ID!): Sale sales(filter: SaleFilterInput, query: String, before: String, after: String, first: Int, last: Int): SaleCountableConnection voucher(id: ID!): Voucher @@ -2957,7 +2976,7 @@ type Query { checkouts(before: String, after: String, first: Int, last: Int): CheckoutCountableConnection checkoutLine(id: ID): CheckoutLine checkoutLines(before: String, after: String, first: Int, last: Int): CheckoutLineCountableConnection - addressValidationRules(countryCode: CountryCode, countryArea: String, cityArea: String): AddressValidationData + addressValidationRules(countryCode: CountryCode!, countryArea: String, city: String, cityArea: String): AddressValidationData customers(filter: CustomerFilterInput, query: String, before: String, after: String, first: Int, last: Int): UserCountableConnection me: User staffUsers(filter: StaffUserInput, query: String, before: String, after: String, first: Int, last: Int): UserCountableConnection @@ -3089,15 +3108,11 @@ input SeoInput { } type SetPassword { - errors: [Error!] + token: String + errors: [Error]! user: User } -input SetPasswordInput { - token: String! - password: String! -} - type ShippingMethod implements Node { id: ID! name: String! @@ -3418,6 +3433,7 @@ enum TransactionKind { REFUND CAPTURE VOID + CONFIRM } union TranslatableItem = Product | Category | Collection | Attribute | AttributeValue | ProductVariant | Page | ShippingMethod | Sale | Voucher | MenuItem @@ -3466,7 +3482,6 @@ type User implements Node { firstName: String! lastName: String! isStaff: Boolean! - token: UUID! isActive: Boolean! note: String dateJoined: DateTime! @@ -3592,13 +3607,14 @@ type Voucher implements Node { applyOncePerCustomer: Boolean! discountValueType: DiscountValueTypeEnum! discountValue: Float! - minAmountSpent: Money + minSpent: Money minCheckoutItemsQuantity: Int categories(before: String, after: String, first: Int, last: Int): CategoryCountableConnection collections(before: String, after: String, first: Int, last: Int): CollectionCountableConnection products(before: String, after: String, first: Int, last: Int): ProductCountableConnection countries: [CountryDisplay] translation(languageCode: LanguageCodeEnum!): VoucherTranslation + minAmountSpent: Money @deprecated(reason: "Use the minSpent field instead.") } type VoucherAddCatalogues { diff --git a/src/components/TableHead/TableHead.tsx b/src/components/TableHead/TableHead.tsx index 9305e263f..0ee28e0ec 100644 --- a/src/components/TableHead/TableHead.tsx +++ b/src/components/TableHead/TableHead.tsx @@ -23,10 +23,10 @@ export interface TableHeadProps extends MuiTableHeadProps { colSpan: number; disabled: boolean; dragRows?: boolean; - selected: number; + selected?: number; items: Node[]; - toolbar: React.ReactNode | React.ReactNodeArray; - toggleAll: (items: Node[], selected: number) => void; + toolbar?: React.ReactNode | React.ReactNodeArray; + toggleAll?: (items: Node[], selected: number) => void; } const styles = (theme: Theme) => @@ -101,25 +101,26 @@ const TableHead = withStyles(styles, { })} /> )} - {(items === undefined || items.length > 0) && ( - - 0) && + (selected && ( + selected && selected > 0 + [classes.checkboxSelected]: selected, + [classes.dragRows]: dragRows })} - checked={selected === 0 ? false : true} - disabled={disabled} - onChange={() => toggleAll(items, selected)} - /> - - )} + > + selected && selected > 0 + })} + checked={selected === 0 ? false : true} + disabled={disabled} + onChange={() => toggleAll(items, selected)} + /> + + ))} {selected ? ( <> )}
-
{toolbar}
+ {toolbar &&
{toolbar}
}
diff --git a/src/config.ts b/src/config.ts index ac5f694cd..7737bfe0d 100644 --- a/src/config.ts +++ b/src/config.ts @@ -21,6 +21,7 @@ export interface AppListViewSettings { [ListViews.NAVIGATION_LIST]: ListSettings; [ListViews.ORDER_LIST]: ListSettings; [ListViews.PAGES_LIST]: ListSettings; + [ListViews.PLUGINS_LIST]: ListSettings; [ListViews.PRODUCT_LIST]: ListSettings; [ListViews.SALES_LIST]: ListSettings; [ListViews.SHIPPING_METHODS_LIST]: ListSettings; @@ -49,6 +50,9 @@ export const defaultListSettings: AppListViewSettings = { [ListViews.PAGES_LIST]: { rowNumber: PAGINATE_BY }, + [ListViews.PLUGINS_LIST]: { + rowNumber: PAGINATE_BY + }, [ListViews.PRODUCT_LIST]: { columns: ["isPublished", "price", "productType"], rowNumber: PAGINATE_BY diff --git a/src/configuration/index.tsx b/src/configuration/index.tsx index 1c991472a..ac23f5bce 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"; @@ -105,6 +107,22 @@ export function createConfigurationMenu(intl: IntlShape): MenuItem[] { permission: PermissionEnum.MANAGE_PAGES, title: intl.formatMessage(sectionNames.pages), url: pageListUrl() + }, + { + description: intl.formatMessage({ + defaultMessage: "View and update your plugins and their settings.", + id: "configurationPluginsPages" + }), + icon: ( + + ), + permission: PermissionEnum.MANAGE_SETTINGS, + title: intl.formatMessage(sectionNames.plugins), + url: pluginsListUrl() } ]; } 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} /> + ) => void; +} + +const useStyles = makeStyles(() => ({ + status: { + paddingTop: 20 + }, + title: { + fontSize: 14, + paddingTop: 10 + } +})); + +const PluginInfo: React.StatelessComponent = ({ + data, + description, + name, + onChange +}) => { + const classes = useStyles({}); + const intl = useIntl(); + return ( + + + + + {intl.formatMessage({ + defaultMessage: "Plugin Name", + description: "plugin name" + })} + + {name} + {description && ( + <> + + {intl.formatMessage({ + defaultMessage: "Plugin Description", + description: "plugin description" + })} + + {description} + + )} + +
+ + {intl.formatMessage({ + defaultMessage: "Status", + description: "plugin status" + })} + + +
+
+ ); +}; +PluginInfo.displayName = "PluginInfo"; +export default PluginInfo; diff --git a/src/plugins/components/PluginInfo/index.ts b/src/plugins/components/PluginInfo/index.ts new file mode 100644 index 000000000..f2a38dc4e --- /dev/null +++ b/src/plugins/components/PluginInfo/index.ts @@ -0,0 +1,2 @@ +export { default } from "./PluginInfo"; +export * from "./PluginInfo"; diff --git a/src/plugins/components/PluginSettings/PluginSettings.tsx b/src/plugins/components/PluginSettings/PluginSettings.tsx new file mode 100644 index 000000000..44d5b4735 --- /dev/null +++ b/src/plugins/components/PluginSettings/PluginSettings.tsx @@ -0,0 +1,87 @@ +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import TextField from "@material-ui/core/TextField"; +import makeStyles from "@material-ui/styles/makeStyles"; +import CardTitle from "@saleor/components/CardTitle"; +import ControlledSwitch from "@saleor/components/ControlledSwitch"; +import { FormErrors } from "@saleor/types"; +import { ConfigurationTypeFieldEnum } from "@saleor/types/globalTypes"; +import React from "react"; +import { useIntl } from "react-intl"; + +import { FormData } from "../PluginsDetailsPage"; + +interface PluginSettingsProps { + data: FormData; + errors: FormErrors<"name" | "configuration">; + disabled: boolean; + onChange: (event: React.ChangeEvent) => void; + fields: Array<{ + name: string; + type: ConfigurationTypeFieldEnum | null; + value: string; + helpText: string | null; + label: string | null; + }>; +} + +const useStyles = makeStyles(() => ({ + item: { + paddingBottom: 10, + paddingTop: 10 + } +})); + +const PluginSettings: React.StatelessComponent = ({ + data, + disabled, + errors, + onChange, + fields +}) => { + const classes = useStyles({}); + const intl = useIntl(); + return ( + + + + {data.configuration.map((configuration, index) => ( +
+ {fields[index].type === ConfigurationTypeFieldEnum.STRING && ( + + )} + {fields[index].type === ConfigurationTypeFieldEnum.BOOLEAN && ( + + )} +
+ ))} +
+
+ ); +}; +PluginSettings.displayName = "PluginSettings"; +export default PluginSettings; diff --git a/src/plugins/components/PluginSettings/index.ts b/src/plugins/components/PluginSettings/index.ts new file mode 100644 index 000000000..422ec9e3c --- /dev/null +++ b/src/plugins/components/PluginSettings/index.ts @@ -0,0 +1,2 @@ +export { default } from "./PluginSettings"; +export * from "./PluginSettings"; diff --git a/src/plugins/components/PluginsDetailsPage/PluginsDetailsPage.tsx b/src/plugins/components/PluginsDetailsPage/PluginsDetailsPage.tsx new file mode 100644 index 000000000..c1f3cc8c4 --- /dev/null +++ b/src/plugins/components/PluginsDetailsPage/PluginsDetailsPage.tsx @@ -0,0 +1,143 @@ +import Typography from "@material-ui/core/Typography"; +import AppHeader from "@saleor/components/AppHeader"; +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 { sectionNames } from "@saleor/intl"; +import { maybe } from "@saleor/misc"; +import { UserError } from "@saleor/types"; +import { ConfigurationItemInput } from "@saleor/types/globalTypes"; +import React from "react"; +import { useIntl } from "react-intl"; + +import { Plugin_plugin } from "../../types/Plugin"; +import PluginInfo from "../PluginInfo"; +import PluginSettings from "../PluginSettings"; + +export interface FormData { + active: boolean; + configuration: ConfigurationItemInput[]; +} + +export interface PluginsDetailsPageProps { + disabled: boolean; + errors: UserError[]; + plugin: Plugin_plugin; + saveButtonBarState: ConfirmButtonTransitionState; + onBack: () => void; + onSubmit: (data: FormData) => void; +} + +const PluginsDetailsPage: React.StatelessComponent = ({ + disabled, + errors, + plugin, + saveButtonBarState, + onBack, + onSubmit +}) => { + const intl = useIntl(); + const initialForm: FormData = { + active: maybe(() => plugin.active, false), + configuration: maybe(() => plugin.configuration, []) + }; + + return ( +
+ {({ data, errors, hasChanged, submit, set, triggerChange }) => { + const onChange = event => { + const newData = { + active: data.active, + configuration: data.configuration + }; + const { name, value } = event.target; + name === "active" + ? (newData.active = value) + : (newData.active = data.active); + if (newData.configuration) { + newData.configuration.map(item => { + if (item.name === name) { + item.value = value; + } + }); + } + triggerChange(); + set(newData); + }; + return ( + + + {intl.formatMessage(sectionNames.plugins)} + + plugin.name, "")} ${intl.formatMessage({ + defaultMessage: "Details", + description: "plugin page title" + })}`} + /> + +
+ + {intl.formatMessage({ + defaultMessage: "Plugin Information and Status", + description: "plugin section title" + })} + + + {intl.formatMessage({ + defaultMessage: + "These are general information about your store. They define what is the URL of your store and what is shown in brow sers taskbar.", + description: "plugin section description" + })} + +
+ plugin.description, "")} + name={maybe(() => plugin.name, "")} + onChange={onChange} + /> + {data.configuration && ( + <> +
+ + {intl.formatMessage({ + defaultMessage: "Plugin Settings", + description: "plugin section title" + })} + + + {intl.formatMessage({ + defaultMessage: + "This adress will be used to generate invoices and calculate shipping rates. Email adress you provide here will be used as a contact adress for your customers.", + description: "plugin section description" + })} + +
+ plugin.configuration, [])} + errors={errors} + disabled={disabled} + onChange={onChange} + /> + + )} +
+ +
+ ); + }} +
+ ); +}; +PluginsDetailsPage.displayName = "PluginsDetailsPage"; +export default PluginsDetailsPage; diff --git a/src/plugins/components/PluginsDetailsPage/index.ts b/src/plugins/components/PluginsDetailsPage/index.ts new file mode 100644 index 000000000..f38ee22f8 --- /dev/null +++ b/src/plugins/components/PluginsDetailsPage/index.ts @@ -0,0 +1,2 @@ +export { default } from "./PluginsDetailsPage"; +export * from "./PluginsDetailsPage"; diff --git a/src/plugins/components/PluginsList/PluginsList.tsx b/src/plugins/components/PluginsList/PluginsList.tsx new file mode 100644 index 000000000..a3b6c9714 --- /dev/null +++ b/src/plugins/components/PluginsList/PluginsList.tsx @@ -0,0 +1,160 @@ +import Card from "@material-ui/core/Card"; +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 EditIcon from "@material-ui/icons/Edit"; +import React from "react"; +import { useIntl } from "react-intl"; + +import Skeleton from "@saleor/components/Skeleton"; +import StatusLabel from "@saleor/components/StatusLabel"; +import TableHead from "@saleor/components/TableHead"; +import TablePagination from "@saleor/components/TablePagination"; +import { translateBoolean } from "@saleor/intl"; +import { maybe, renderCollection } from "@saleor/misc"; +import { ListProps } from "@saleor/types"; +import { Plugins_plugins_edges_node } from "../../types/Plugins"; + +export interface PluginListProps extends ListProps { + plugins: Plugins_plugins_edges_node[]; +} + +const styles = (theme: Theme) => + createStyles({ + [theme.breakpoints.up("lg")]: { + colAction: { + "& svg": { + color: theme.palette.primary.main + }, + textAlign: "right" + }, + colActive: {}, + colName: {} + }, + colAction: {}, + colActive: {}, + colName: {}, + link: { + cursor: "pointer" + } + }); + +const numberOfColumns = 4; + +const PluginList = withStyles(styles, { name: "PluginList" })( + ({ + classes, + settings, + plugins, + disabled, + onNextPage, + pageInfo, + onRowClick, + onUpdateListSettings, + onPreviousPage + }: PluginListProps & WithStyles) => { + const intl = useIntl(); + return ( + + + + + {intl.formatMessage({ + defaultMessage: "Name", + description: "plugin list table header" + })} + + + {intl.formatMessage({ + defaultMessage: "Active", + description: "plugin list table header" + })} + + + {intl.formatMessage({ + defaultMessage: "Action", + description: "plugin list table header" + })} + + + + + + + + + {renderCollection( + plugins, + plugin => { + return ( + + + {maybe(() => plugin.name, )} + + + {maybe( + () => ( + + ), + + )} + + +
+ +
+
+
+ ); + }, + () => ( + + + {intl.formatMessage({ + defaultMessage: "No plugins found", + description: "plugin no 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..7ff0735ad --- /dev/null +++ b/src/plugins/components/PluginsListPage/PluginsListPage.tsx @@ -0,0 +1,52 @@ +import React from "react"; +import { useIntl } from "react-intl"; + +import AppHeader from "@saleor/components/AppHeader"; +import Container from "@saleor/components/Container"; +import PageHeader from "@saleor/components/PageHeader"; +import { sectionNames } from "@saleor/intl"; +import { PageListProps } from "@saleor/types"; +import { Plugins_plugins_edges_node } from "../../types/Plugins"; +import PluginsList from "../PluginsList/PluginsList"; + +export interface PluginsListPageProps extends PageListProps { + plugins: Plugins_plugins_edges_node[]; + onBack: () => void; +} + +const PluginsListPage: React.StatelessComponent = ({ + disabled, + settings, + onBack, + onNextPage, + onPreviousPage, + onRowClick, + onUpdateListSettings, + pageInfo, + plugins +}) => { + const intl = useIntl(); + return ( + + + {intl.formatMessage({ + defaultMessage: "Configuration", + description: "plugin back button" + })} + + + + + ); +}; +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/fixtures.ts b/src/plugins/fixtures.ts new file mode 100644 index 000000000..4da3ce27b --- /dev/null +++ b/src/plugins/fixtures.ts @@ -0,0 +1,57 @@ +import { ConfigurationTypeFieldEnum } from "@saleor/types/globalTypes"; +import { Plugin_plugin } from "./types/Plugin"; +import { Plugins_plugins_edges_node } from "./types/Plugins"; + +export const pluginList: Plugins_plugins_edges_node[] = [ + { + __typename: "Plugin", + active: true, + description: + "Lorem ipsum dolor sit amet enim. Etiam ullamcorper. Suspendisse a pellentesque dui, non felis. Maecenas malesuada elit lectus felis, malesuada ultricies. Curabitur et ligula. Ut molestie a, ultricies porta urna. Vestibulum commodo volutpat a, convallis ac, laoreet enim. Phasellus fermentum in, dolor. Pellentesque facilisis. Nulla imperdiet sit amet magna.", + id: "Jzx123sEt==", + name: "Avalara" + }, + { + __typename: "Plugin", + active: false, + description: + "Lorem ipsum dolor sit amet enim. Etiam ullamcorper. Suspendisse a pellentesque dui, non felis. Maecenas malesuada elit lectus felis, malesuada ultricies. Curabitur et ligula. Ut molestie a, ultricies porta urna. Vestibulum commodo volutpat a, convallis ac, laoreet enim. Phasellus fermentum in, dolor. Pellentesque facilisis. Nulla imperdiet sit amet magna.", + id: "Jzx123sEt==", + name: "VatLayer" + } +]; +export const plugin: Plugin_plugin = { + __typename: "Plugin", + active: true, + configuration: [ + { + __typename: "ConfigurationItem", + helpText: "Provide user or account details", + label: "Username or account", + name: "Username or account", + type: ConfigurationTypeFieldEnum.STRING, + value: "" + }, + { + __typename: "ConfigurationItem", + helpText: "Provide password or license details", + label: "Password or license", + name: "Password or license", + type: ConfigurationTypeFieldEnum.STRING, + value: "" + }, + { + __typename: "ConfigurationItem", + helpText: "Determines if Saleor should use Avatax sandbox API.", + label: "Use sandbox", + name: "Use sandbox", + type: ConfigurationTypeFieldEnum.BOOLEAN, + value: "true" + } + ], + description: + "Lorem ipsum dolor sit amet enim. Etiam ullamcorper. Suspendisse a pellentesque dui, non felis. Maecenas malesuada elit lectus felis, malesuada ultricies. Curabitur et ligula. Ut molestie a, ultricies porta urna. Vestibulum commodo volutpat a, convallis ac, laoreet enim. Phasellus fermentum in, dolor. Pellentesque facilisis. Nulla imperdiet sit amet magna.", + + id: "UGx1Z2luQ29uZmlndXJhdGlvbjoy", + name: "Username or account" +}; diff --git a/src/plugins/index.tsx b/src/plugins/index.tsx new file mode 100644 index 000000000..4f2276264 --- /dev/null +++ b/src/plugins/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 { + pluginsListPath, + PluginsListUrlQueryParams, + pluginsPath +} 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 = () => { + const intl = useIntl(); + return ( + <> + + + + + + + ); +}; + +export default Component; diff --git a/src/plugins/mutations.ts b/src/plugins/mutations.ts new file mode 100644 index 000000000..9a23fada4 --- /dev/null +++ b/src/plugins/mutations.ts @@ -0,0 +1,24 @@ +import gql from "graphql-tag"; + +import { TypedMutation } from "../mutations"; +import { pluginsDetailsFragment } from "./queries"; +import { PluginUpdate, PluginUpdateVariables } from "./types/PluginUpdate"; + +const pluginUpdate = gql` + ${pluginsDetailsFragment} + mutation PluginUpdate($id: ID!, $input: PluginUpdateInput!) { + pluginUpdate(id: $id, input: $input) { + errors { + field + message + } + plugin { + ...PluginsDetailsFragment + } + } + } +`; +export const TypedPluginUpdate = TypedMutation< + PluginUpdate, + PluginUpdateVariables +>(pluginUpdate); diff --git a/src/plugins/queries.ts b/src/plugins/queries.ts new file mode 100644 index 000000000..ec3ae773a --- /dev/null +++ b/src/plugins/queries.ts @@ -0,0 +1,62 @@ +import gql from "graphql-tag"; + +import { TypedQuery } from "../queries"; +import { Plugin, PluginVariables } from "./types/Plugin"; +import { Plugins, PluginsVariables } from "./types/Plugins"; + +export const pluginsFragment = gql` + fragment PluginFragment on Plugin { + id + name + description + active + } +`; + +export const pluginsDetailsFragment = gql` + ${pluginsFragment} + fragment PluginsDetailsFragment on Plugin { + ...PluginFragment + configuration { + name + type + value + helpText + label + } + } +`; + +const pluginsList = gql` + ${pluginsFragment} + query Plugins($first: Int, $after: String, $last: Int, $before: String) { + plugins(before: $before, after: $after, first: $first, last: $last) { + edges { + node { + ...PluginFragment + } + } + pageInfo { + hasPreviousPage + hasNextPage + startCursor + endCursor + } + } + } +`; +export const TypedPluginsListQuery = TypedQuery( + pluginsList +); + +const pluginsDetails = gql` + ${pluginsDetailsFragment} + query Plugin($id: ID!) { + plugin(id: $id) { + ...PluginsDetailsFragment + } + } +`; +export const TypedPluginsDetailsQuery = TypedQuery( + pluginsDetails +); diff --git a/src/plugins/types/Plugin.ts b/src/plugins/types/Plugin.ts new file mode 100644 index 000000000..66fc7b47f --- /dev/null +++ b/src/plugins/types/Plugin.ts @@ -0,0 +1,35 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { ConfigurationTypeFieldEnum } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL query operation: Plugin +// ==================================================== + +export interface Plugin_plugin_configuration { + __typename: "ConfigurationItem"; + name: string; + type: ConfigurationTypeFieldEnum | null; + value: string; + helpText: string | null; + label: string | null; +} + +export interface Plugin_plugin { + __typename: "Plugin"; + id: string; + name: string; + description: string; + active: boolean; + configuration: (Plugin_plugin_configuration | null)[] | null; +} + +export interface Plugin { + plugin: Plugin_plugin | null; +} + +export interface PluginVariables { + id: string; +} diff --git a/src/plugins/types/PluginUpdate.ts b/src/plugins/types/PluginUpdate.ts new file mode 100644 index 000000000..90337d98d --- /dev/null +++ b/src/plugins/types/PluginUpdate.ts @@ -0,0 +1,48 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { PluginUpdateInput, ConfigurationTypeFieldEnum } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL mutation operation: PluginUpdate +// ==================================================== + +export interface PluginUpdate_pluginUpdate_errors { + __typename: "Error"; + field: string | null; + message: string | null; +} + +export interface PluginUpdate_pluginUpdate_plugin_configuration { + __typename: "ConfigurationItem"; + name: string; + type: ConfigurationTypeFieldEnum | null; + value: string; + helpText: string | null; + label: string | null; +} + +export interface PluginUpdate_pluginUpdate_plugin { + __typename: "Plugin"; + id: string; + name: string; + description: string; + active: boolean; + configuration: (PluginUpdate_pluginUpdate_plugin_configuration | null)[] | null; +} + +export interface PluginUpdate_pluginUpdate { + __typename: "PluginUpdate"; + errors: PluginUpdate_pluginUpdate_errors[] | null; + plugin: PluginUpdate_pluginUpdate_plugin | null; +} + +export interface PluginUpdate { + pluginUpdate: PluginUpdate_pluginUpdate | null; +} + +export interface PluginUpdateVariables { + id: string; + input: PluginUpdateInput; +} diff --git a/src/plugins/types/Plugins.ts b/src/plugins/types/Plugins.ts new file mode 100644 index 000000000..c8861ad45 --- /dev/null +++ b/src/plugins/types/Plugins.ts @@ -0,0 +1,45 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL query operation: Plugins +// ==================================================== + +export interface Plugins_plugins_edges_node { + __typename: "Plugin"; + id: string; + name: string; + description: string; + active: boolean; +} + +export interface Plugins_plugins_edges { + __typename: "PluginCountableEdge"; + node: Plugins_plugins_edges_node; +} + +export interface Plugins_plugins_pageInfo { + __typename: "PageInfo"; + hasPreviousPage: boolean; + hasNextPage: boolean; + startCursor: string | null; + endCursor: string | null; +} + +export interface Plugins_plugins { + __typename: "PluginCountableConnection"; + edges: Plugins_plugins_edges[]; + pageInfo: Plugins_plugins_pageInfo; +} + +export interface Plugins { + plugins: Plugins_plugins | null; +} + +export interface PluginsVariables { + 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..52d893eb5 --- /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: "Plugin"; + id: string; + name: string; + description: string; + active: boolean; +} diff --git a/src/plugins/types/pluginsDetailsFragment.ts b/src/plugins/types/pluginsDetailsFragment.ts new file mode 100644 index 000000000..35158f77f --- /dev/null +++ b/src/plugins/types/pluginsDetailsFragment.ts @@ -0,0 +1,27 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { ConfigurationTypeFieldEnum } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL fragment: pluginsDetailsFragment +// ==================================================== + +export interface pluginsDetailsFragment_configuration { + __typename: "ConfigurationItem"; + name: string; + type: ConfigurationTypeFieldEnum | null; + value: string; + helpText: string | null; + label: string | null; +} + +export interface pluginsDetailsFragment { + __typename: "Plugin"; + id: string; + name: string; + description: string; + active: boolean; + configuration: (pluginsDetailsFragment_configuration | null)[] | null; +} diff --git a/src/plugins/urls.ts b/src/plugins/urls.ts new file mode 100644 index 000000000..e1362170d --- /dev/null +++ b/src/plugins/urls.ts @@ -0,0 +1,16 @@ +import { stringify as stringifyQs } from "qs"; +import urlJoin from "url-join"; + +import { Pagination, SingleAction } from "../types"; + +export const pluginsSection = "/plugins/"; + +export const pluginsListPath = pluginsSection; +export type PluginsListUrlQueryParams = Pagination & SingleAction; +export const pluginsListUrl = (params?: PluginsListUrlQueryParams) => + pluginsListPath + "?" + stringifyQs(params); + +export const pluginsPath = (id: string) => urlJoin(pluginsSection, id); +export type PluginsUrlQueryParams = SingleAction; +export const pluginsUrl = (id: string, params?: PluginsUrlQueryParams) => + pluginsPath(encodeURIComponent(id)) + "?" + stringifyQs(params); diff --git a/src/plugins/views/PluginsDetails.tsx b/src/plugins/views/PluginsDetails.tsx new file mode 100644 index 000000000..69e136351 --- /dev/null +++ b/src/plugins/views/PluginsDetails.tsx @@ -0,0 +1,100 @@ +import { WindowTitle } from "@saleor/components/WindowTitle"; +import useNavigator from "@saleor/hooks/useNavigator"; +import useNotifier from "@saleor/hooks/useNotifier"; +import React from "react"; +import { useIntl } from "react-intl"; + +import { getMutationState, maybe } from "../../misc"; +import PluginsDetailsPage from "../components/PluginsDetailsPage"; +import { TypedPluginUpdate } from "../mutations"; +import { TypedPluginsDetailsQuery } from "../queries"; +import { pluginsListUrl, PluginsListUrlQueryParams } from "../urls"; + +export interface PluginsDetailsProps { + id: string; + params: PluginsListUrlQueryParams; +} + +export const PluginsDetails: React.StatelessComponent = ({ + id +}) => { + const navigate = useNavigator(); + const notify = useNotifier(); + const intl = useIntl(); + + return ( + + {(pluginUpdate, pluginUpdateOpts) => ( + + {PluginDetails => { + const formTransitionState = getMutationState( + pluginUpdateOpts.called, + pluginUpdateOpts.loading, + maybe(() => pluginUpdateOpts.data.pluginUpdate.errors) + ); + + const formErrors = maybe( + () => pluginUpdateOpts.data.pluginUpdate.errors, + [] + ); + + if (formErrors.length) { + formErrors.map(error => { + notify({ + text: error.message + }); + }); + } else { + if (pluginUpdateOpts.data) { + notify({ + text: intl.formatMessage({ + defaultMessage: "Succesfully updated plugin settings", + description: "plugin success message" + }) + }); + } + } + + return ( + <> + PluginDetails.data.plugin.name)} + /> + PluginDetails.data.plugin)} + onBack={() => navigate(pluginsListUrl())} + onSubmit={formData => { + const configurationInput = + formData.configuration && + formData.configuration.map(item => { + return { + name: item.name, + value: item.value.toString() + }; + }); + pluginUpdate({ + variables: { + id, + input: { + active: formData.active, + configuration: configurationInput + ? configurationInput + : null + } + } + }); + }} + /> + + ); + }} + + )} + + ); +}; +PluginsDetails.displayName = "PluginsDetails"; +export default PluginsDetails; diff --git a/src/plugins/views/PluginsList.tsx b/src/plugins/views/PluginsList.tsx new file mode 100644 index 000000000..848904d5a --- /dev/null +++ b/src/plugins/views/PluginsList.tsx @@ -0,0 +1,58 @@ +import { configurationMenuUrl } from "@saleor/configuration"; +import useListSettings from "@saleor/hooks/useListSettings"; +import useNavigator from "@saleor/hooks/useNavigator"; +import usePaginator, { + createPaginationState +} from "@saleor/hooks/usePaginator"; +import { maybe } from "@saleor/misc"; +import { ListViews } from "@saleor/types"; +import React from "react"; + +import PluginsListPage from "../components/PluginsListPage/PluginsListPage"; +import { TypedPluginsListQuery } from "../queries"; +import { PluginsListUrlQueryParams, pluginsUrl } from "../urls"; + +interface PluginsListProps { + params: PluginsListUrlQueryParams; +} + +export const PluginsList: React.StatelessComponent = ({ + params +}) => { + const navigate = useNavigator(); + const paginate = usePaginator(); + const { updateListSettings, settings } = useListSettings( + ListViews.PLUGINS_LIST + ); + const paginationState = createPaginationState(settings.rowNumber, params); + + return ( + + {({ data, loading }) => { + const { loadNextPage, loadPreviousPage, pageInfo } = paginate( + maybe(() => data.plugins.pageInfo), + paginationState, + params + ); + return ( + <> + data.plugins.edges.map(edge => edge.node))} + pageInfo={pageInfo} + onAdd={() => navigate(configurationMenuUrl)} + onBack={() => navigate(configurationMenuUrl)} + onNextPage={loadNextPage} + onPreviousPage={loadPreviousPage} + onUpdateListSettings={updateListSettings} + onRowClick={id => () => navigate(pluginsUrl(id))} + /> + + ); + }} + + ); +}; + +export default PluginsList; diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 0b6f69afe..d7be04135 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -10729,6 +10729,7 @@ exports[`Storyshots Views / Attributes / Attribute list default 1`] = `
+ 0 @@ -10738,22 +10739,6 @@ exports[`Storyshots Views / Attributes / Attribute list default 1`] = ` -
- - + 0 @@ -11463,24 +11449,6 @@ exports[`Storyshots Views / Attributes / Attribute list loading 1`] = ` -
- - + 0 @@ -12418,22 +12387,6 @@ exports[`Storyshots Views / Categories / Category list default 1`] = ` -
- - + 0 @@ -13066,22 +13020,6 @@ exports[`Storyshots Views / Categories / Category list loading 1`] = ` -
- - + 0 @@ -15748,24 +15687,6 @@ Ctrl + K" -
- - + 0 @@ -19809,22 +19731,6 @@ Ctrl + K" -
- - + 0 @@ -21354,22 +21261,6 @@ Ctrl + K" -
- - + 0 @@ -22502,24 +22394,6 @@ Ctrl + K" -
- - + 0 @@ -24120,22 +23995,6 @@ exports[`Storyshots Views / Collections / Collection list default 1`] = ` -
- - + 0 @@ -24574,24 +24434,6 @@ exports[`Storyshots Views / Collections / Collection list loading 1`] = ` -
- - +
+
+
+ +
+
+

+ Plugins +

+

+ View and update your plugins and their settings. +

+
+
+
@@ -34340,6 +34223,7 @@ exports[`Storyshots Views / Customers / Customer list default 1`] = `
+ 0 @@ -34349,22 +34233,6 @@ exports[`Storyshots Views / Customers / Customer list default 1`] = ` -
- - + 0 @@ -35391,24 +35260,6 @@ exports[`Storyshots Views / Customers / Customer list loading 1`] = ` -
- - + 0 @@ -36970,22 +36822,6 @@ exports[`Storyshots Views / Discounts / Sale details collections 1`] = ` -
- - + 0 @@ -37562,22 +37399,6 @@ exports[`Storyshots Views / Discounts / Sale details default 1`] = ` -
- - + 0 @@ -38174,22 +37996,6 @@ exports[`Storyshots Views / Discounts / Sale details form errors 1`] = ` -
- - + 0 @@ -38775,24 +38582,6 @@ exports[`Storyshots Views / Discounts / Sale details loading 1`] = ` -
- - + 0 @@ -39397,22 +39187,6 @@ exports[`Storyshots Views / Discounts / Sale details products 1`] = ` -
- - + 0 @@ -40008,22 +39783,6 @@ exports[`Storyshots Views / Discounts / Sale list default 1`] = ` -
- - + 0 @@ -40464,22 +40224,6 @@ exports[`Storyshots Views / Discounts / Sale list loading 1`] = ` -
- - + 0 @@ -45373,22 +45118,6 @@ exports[`Storyshots Views / Discounts / Voucher list default 1`] = ` -
- - + 0 @@ -45726,22 +45456,6 @@ exports[`Storyshots Views / Discounts / Voucher list loading 1`] = ` -
- - + 0 @@ -48943,22 +48658,6 @@ exports[`Storyshots Views / Navigation / Menu list default 1`] = ` -
- - + 0 @@ -49279,24 +48979,6 @@ exports[`Storyshots Views / Navigation / Menu list loading 1`] = ` -
- - + 0 @@ -49837,22 +49520,6 @@ exports[`Storyshots Views / Orders / Draft order list default 1`] = ` -
- - + 0 @@ -50969,24 +50637,6 @@ exports[`Storyshots Views / Orders / Draft order list loading 1`] = ` -
- - + 0 @@ -66634,22 +66285,6 @@ exports[`Storyshots Views / Orders / Order list default 1`] = ` -
- - + 0 @@ -68259,24 +67895,6 @@ exports[`Storyshots Views / Orders / Order list loading 1`] = ` -
- - + 0 @@ -69521,22 +69140,6 @@ exports[`Storyshots Views / Orders / Order list with custom filters 1`] = ` -
- - + 0 @@ -73354,22 +72958,6 @@ exports[`Storyshots Views / Pages / Page list default 1`] = ` -
- - + 0 @@ -73732,24 +73321,6 @@ exports[`Storyshots Views / Pages / Page list loading 1`] = ` -
- - `; +exports[`Storyshots Views / Plugins / Plugin details default 1`] = ` +
+
+
+
+
+ Username or account Details +
+
+
+
+
+
+
+
+ Plugin Information and Status +
+

+ These are general information about your store. They define what is the URL of your store and what is shown in brow sers taskbar. +

+
+
+
+ + Plugin Information and Status + +
+
+
+
+
+
+ Plugin Name +
+

+ Username or account +

+
+ Plugin Description +
+

+ Lorem ipsum dolor sit amet enim. Etiam ullamcorper. Suspendisse a pellentesque dui, non felis. Maecenas malesuada elit lectus felis, malesuada ultricies. Curabitur et ligula. Ut molestie a, ultricies porta urna. Vestibulum commodo volutpat a, convallis ac, laoreet enim. Phasellus fermentum in, dolor. Pellentesque facilisis. Nulla imperdiet sit amet magna. +

+
+
+

+ Status +

+
+
+
+ Plugin Settings +
+

+ This adress will be used to generate invoices and calculate shipping rates. Email adress you provide here will be used as a contact adress for your customers. +

+
+
+
+ + Plugin Settings + +
+
+
+
+
+
+
+ +
+ + +
+

+ Provide user or account details +

+
+
+
+
+ +
+ + +
+

+ Provide password or license details +

+
+
+
+
+
+
+
+ +
+`; + +exports[`Storyshots Views / Plugins / Plugin details form errors 1`] = ` +
+
+
+
+
+ Username or account Details +
+
+
+
+
+
+
+
+ Plugin Information and Status +
+

+ These are general information about your store. They define what is the URL of your store and what is shown in brow sers taskbar. +

+
+
+
+ + Plugin Information and Status + +
+
+
+
+
+
+ Plugin Name +
+

+ Username or account +

+
+ Plugin Description +
+

+ Lorem ipsum dolor sit amet enim. Etiam ullamcorper. Suspendisse a pellentesque dui, non felis. Maecenas malesuada elit lectus felis, malesuada ultricies. Curabitur et ligula. Ut molestie a, ultricies porta urna. Vestibulum commodo volutpat a, convallis ac, laoreet enim. Phasellus fermentum in, dolor. Pellentesque facilisis. Nulla imperdiet sit amet magna. +

+
+
+

+ Status +

+
+
+
+ Plugin Settings +
+

+ This adress will be used to generate invoices and calculate shipping rates. Email adress you provide here will be used as a contact adress for your customers. +

+
+
+
+ + Plugin Settings + +
+
+
+
+
+
+
+ +
+ + +
+

+ Provide user or account details +

+
+
+
+
+ +
+ + +
+

+ Provide password or license details +

+
+
+
+
+
+
+
+ +
+`; + +exports[`Storyshots Views / Plugins / Plugin details loading 1`] = ` +
+
+
+
+
+ Details +
+
+
+
+
+
+
+
+ Plugin Information and Status +
+

+ These are general information about your store. They define what is the URL of your store and what is shown in brow sers taskbar. +

+
+
+
+ + Plugin Information and Status + +
+
+
+
+
+
+ Plugin Name +
+

+

+
+

+ Status +

+
+
+
+ Plugin Settings +
+

+ This adress will be used to generate invoices and calculate shipping rates. Email adress you provide here will be used as a contact adress for your customers. +

+
+
+
+ + Plugin Settings + +
+
+
+
+
+
+
+
+ +
+`; + +exports[`Storyshots Views / Plugins / Plugin list default 1`] = ` +
+
+
+
+ Plugins +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Active + + Action +
+
+
+
+`; + +exports[`Storyshots Views / Plugins / Plugin list loading 1`] = ` +
+
+
+
+ Plugins +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + +
+ Name + + Active + + Action +
+ + ‌ + + + + ‌ + + +
+ +
+
+
+
+
+`; + +exports[`Storyshots Views / Plugins / Plugin list no data 1`] = ` +
+
+
+
+ Plugins +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + +
+ Name + + Active + + Action +
+ No plugins found +
+
+
+
+`; + exports[`Storyshots Views / Product types / Create product type default 1`] = `
+ 0 @@ -75104,22 +76154,6 @@ exports[`Storyshots Views / Product types / Product type details default 1`] = ` class="MuiTableCell-root-id MuiTableCell-head-id" scope="col" /> -
- - + 0 @@ -75740,22 +76775,6 @@ exports[`Storyshots Views / Product types / Product type details form errors 1`] class="MuiTableCell-root-id MuiTableCell-head-id" scope="col" /> -
- - + 0 @@ -76377,24 +77397,6 @@ exports[`Storyshots Views / Product types / Product type details loading 1`] = ` class="MuiTableCell-root-id MuiTableCell-head-id" scope="col" /> -
- - + 0 @@ -77006,22 +78009,6 @@ exports[`Storyshots Views / Product types / Product types list default 1`] = ` -
- - + 0 @@ -77387,24 +78375,6 @@ exports[`Storyshots Views / Product types / Product types list loading 1`] = ` -
- - + 0 @@ -83976,22 +84947,6 @@ Ctrl + K" -
- - + 0 @@ -86712,22 +87668,6 @@ Ctrl + K" -
- - + 0 @@ -89512,22 +90453,6 @@ exports[`Storyshots Views / Products / Product list default 1`] = ` -
- - + 0 @@ -90519,24 +91445,6 @@ exports[`Storyshots Views / Products / Product list loading 1`] = ` -
- - + 0 @@ -91874,22 +92783,6 @@ exports[`Storyshots Views / Products / Product list with custom filters 1`] = ` -
- - + 0 @@ -96720,22 +97614,6 @@ exports[`Storyshots Views / Shipping / Shipping zones list default 1`] = ` -
- - + 0 @@ -97339,24 +98218,6 @@ exports[`Storyshots Views / Shipping / Shipping zones list loading 1`] = ` -
- - undefined, + onSubmit: () => undefined, + plugin, + saveButtonBarState: "default" +}; + +storiesOf("Views / Plugins / Plugin details", module) + .addDecorator(Decorator) + .add("default", () => ) + .add("loading", () => ( + + )) + .add("form errors", () => ( + ).map(formError)} + /> + )); diff --git a/src/storybook/stories/plugins/PluginsListPage.tsx b/src/storybook/stories/plugins/PluginsListPage.tsx new file mode 100644 index 000000000..fd82f58c9 --- /dev/null +++ b/src/storybook/stories/plugins/PluginsListPage.tsx @@ -0,0 +1,23 @@ +import { storiesOf } from "@storybook/react"; +import React from "react"; + +import { pageListProps } from "../../../fixtures"; +import PluginsListPage, { + PluginsListPageProps +} from "../../../plugins/components/PluginsListPage"; +import { pluginList } from "../../../plugins/fixtures"; +import Decorator from "../../Decorator"; + +const props: PluginsListPageProps = { + ...pageListProps.default, + onBack: () => undefined, + plugins: pluginList +}; + +storiesOf("Views / Plugins / Plugin list", module) + .addDecorator(Decorator) + .add("default", () => ) + .add("loading", () => ( + + )) + .add("no data", () => ); diff --git a/src/types.ts b/src/types.ts index dad6b8952..8a6b01353 100644 --- a/src/types.ts +++ b/src/types.ts @@ -23,6 +23,7 @@ export enum ListViews { NAVIGATION_LIST = "NAVIGATION_LIST", ORDER_LIST = "ORDER_LIST", PAGES_LIST = "PAGES_LIST", + PLUGINS_LIST = "PLUGIN_LIST", PRODUCT_LIST = "PRODUCT_LIST", SALES_LIST = "SALES_LIST", SHIPPING_METHODS_LIST = "SHIPPING_METHODS_LIST", diff --git a/src/types/globalTypes.ts b/src/types/globalTypes.ts index ee1d14bb7..6fa1712cc 100644 --- a/src/types/globalTypes.ts +++ b/src/types/globalTypes.ts @@ -33,6 +33,11 @@ export enum AuthorizationKeyType { GOOGLE_OAUTH2 = "GOOGLE_OAUTH2", } +export enum ConfigurationTypeFieldEnum { + BOOLEAN = "BOOLEAN", + STRING = "STRING", +} + export enum DiscountValueTypeEnum { FIXED = "FIXED", PERCENTAGE = "PERCENTAGE", @@ -340,6 +345,11 @@ export interface CollectionInput { publicationDate?: any | null; } +export interface ConfigurationItemInput { + name: string; + value: string; +} + export interface CustomerInput { defaultBillingAddress?: AddressInput | null; defaultShippingAddress?: AddressInput | null; @@ -466,6 +476,11 @@ export interface PageTranslationInput { contentJson?: any | null; } +export interface PluginUpdateInput { + active?: boolean | null; + configuration?: (ConfigurationItemInput | null)[] | null; +} + export interface PriceRangeInput { gte?: number | null; lte?: number | null; @@ -480,6 +495,7 @@ export interface ProductFilterInput { stockAvailability?: StockAvailability | null; productType?: string | null; search?: string | null; + minimalPrice?: PriceRangeInput | null; } export interface ProductTypeInput {