Merge branch 'master' into fix/voucher-limit

This commit is contained in:
Marcin Gębala 2019-11-26 16:06:05 +01:00 committed by GitHub
commit c875d6867c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 2170 additions and 389 deletions

1
.gitignore vendored
View file

@ -13,6 +13,7 @@
!.nvmrc
!.npmrc
!.plop
!.prettierignore
!.pylintrc
!.travis*
!.testcafe

1
.prettierignore Normal file
View file

@ -0,0 +1 @@
types/

View file

@ -13,6 +13,7 @@ All notable, unreleased changes to this project will be documented in this file.
- Fix disappearing products description - #259 by @dominik-zeglen
- Improve mobile appearance - #240 by @benekex2 and @dominik-zeglen
- Use searches as hooks instead of components - #262 by @dominik-zeglen
- Add navigator - #267 by @dominik-zeglen
- Fix voucher limit - #271 by @dominik-zeglen
## 2.0.0

View file

@ -1,6 +1,6 @@
msgid ""
msgstr ""
"POT-Creation-Date: 2019-11-13T13:13:30.128Z\n"
"POT-Creation-Date: 2019-11-26T14:34:48.426Z\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"MIME-Version: 1.0\n"
@ -167,14 +167,6 @@ msgctxt "page header"
msgid "Add Collection"
msgstr ""
#: build/locale/src/customers/components/CustomerCreatePage/CustomerCreatePage.json
#. [src.customers.components.CustomerCreatePage.2622255457] - page header
#. defaultMessage is:
#. Add Customer
msgctxt "page header"
msgid "Add Customer"
msgstr ""
#: build/locale/src/components/Filter/Filter.json
#. [src.components.Filter.2852521946] - button
#. defaultMessage is:
@ -263,14 +255,6 @@ msgctxt "button"
msgid "Add authentication"
msgstr ""
#: build/locale/src/customers/components/CustomerListPage/CustomerListPage.json
#. [src.customers.components.CustomerListPage.1934221653] - button
#. defaultMessage is:
#. Add customer
msgctxt "button"
msgid "Add customer"
msgstr ""
#: build/locale/src/components/Filter/FilterContent.json
#. [src.components.Filter.2851720415] - button
#. defaultMessage is:
@ -856,21 +840,9 @@ msgid "Are you sure you want to delete {counter,plural,one{this attribute} other
msgstr ""
#: build/locale/src/categories/views/CategoryDetails.json
#. [src.categories.views.982216972]
#. [src.categories.views.1592907702]
#. defaultMessage is:
#. Are you sure you want to delete {counter,plural,one{this attribute} other{{displayQuantity} categories}}?
msgctxt "description"
msgid "Are you sure you want to delete {counter,plural,one{this attribute} other{{displayQuantity} categories}}?"
msgstr ""
#: build/locale/src/categories/views/CategoryDetails.json
#. [src.categories.views.3920301974]
#. defaultMessage is:
#. Are you sure you want to delete {counter,plural,one{this attribute} other{{displayQuantity} products}}?
msgctxt "description"
msgid "Are you sure you want to delete {counter,plural,one{this attribute} other{{displayQuantity} products}}?"
msgstr ""
#. Are you sure you want to delete {counter,plural,one{this category} other{{displayQuantity} categories}}?
#: build/locale/src/categories/views/CategoryList/CategoryList.json
#. [src.categories.views.CategoryList.1592907702]
#. defaultMessage is:
@ -927,6 +899,14 @@ msgctxt "dialog content"
msgid "Are you sure you want to delete {counter,plural,one{this product type} other{{displayQuantity} product types}}?"
msgstr ""
#: build/locale/src/categories/views/CategoryDetails.json
#. [src.categories.views.785143617]
#. defaultMessage is:
#. Are you sure you want to delete {counter,plural,one{this product} other{{displayQuantity} products}}?
msgctxt "description"
msgid "Are you sure you want to delete {counter,plural,one{this product} other{{displayQuantity} products}}?"
msgstr ""
#: build/locale/src/products/views/ProductList/ProductList.json
#. [src.products.views.ProductList.785143617] - dialog content
#. defaultMessage is:
@ -1803,6 +1783,10 @@ msgctxt "number of categories"
msgid "Categories ({quantity})"
msgstr ""
#: build/locale/src/components/Navigator/modes/messages.json
#. [src.components.Navigator.modes.category]
#. defaultMessage is:
#. Category
#: build/locale/src/products/components/ProductCategoryAndCollectionsForm/ProductCategoryAndCollectionsForm.json
#. [src.products.components.ProductCategoryAndCollectionsForm.1755013298]
#. defaultMessage is:
@ -1967,6 +1951,14 @@ msgctxt "voucher code"
msgid "Code"
msgstr ""
#: build/locale/src/components/Navigator/modes/messages.json
#. [src.components.Navigator.modes.collection]
#. defaultMessage is:
#. Collection
msgctxt "description"
msgid "Collection"
msgstr ""
#: build/locale/src/translations/components/TranslationsCollectionsPage/TranslationsCollectionsPage.json
#. [src.translations.components.TranslationsCollectionsPage.2759199473]
#. defaultMessage is:
@ -2347,6 +2339,38 @@ msgctxt "create service token, button"
msgid "Create"
msgstr ""
#: build/locale/src/components/Navigator/modes/messages.json
#. [src.components.Navigator.modes.createCategory] - button
#. defaultMessage is:
#. Create Category
msgctxt "button"
msgid "Create Category"
msgstr ""
#: build/locale/src/components/Navigator/modes/messages.json
#. [src.components.Navigator.modes.createCollection] - button
#. defaultMessage is:
#. Create Collection
msgctxt "button"
msgid "Create Collection"
msgstr ""
#: build/locale/src/components/Navigator/modes/messages.json
#. [src.components.Navigator.modes.createCustomer] - button
#. defaultMessage is:
#. Create Customer
msgctxt "button"
msgid "Create Customer"
msgstr ""
#: build/locale/src/customers/components/CustomerCreatePage/CustomerCreatePage.json
#. [src.customers.components.CustomerCreatePage.4025686004] - page header
#. defaultMessage is:
#. Create Customer
msgctxt "page header"
msgid "Create Customer"
msgstr ""
#: build/locale/src/navigation/components/MenuCreateDialog/MenuCreateDialog.json
#. [menuCreateDialogHeader] - dialog header
#. defaultMessage is:
@ -2395,6 +2419,14 @@ msgctxt "header"
msgid "Create New Shipping Zone"
msgstr ""
#: build/locale/src/components/Navigator/modes/messages.json
#. [src.components.Navigator.modes.createOrder] - button
#. defaultMessage is:
#. Create Order
msgctxt "button"
msgid "Create Order"
msgstr ""
#: build/locale/src/pages/components/PageDetailsPage/PageDetailsPage.json
#. [src.pages.components.PageDetailsPage.1068617485] - page header
#. defaultMessage is:
@ -2411,14 +2443,10 @@ msgctxt "header"
msgid "Create Page"
msgstr ""
#: build/locale/src/orders/components/OrderProductAddDialog/OrderProductAddDialog.json
#. [src.orders.components.OrderProductAddDialog.1542417144] - dialog header
#: build/locale/src/components/Navigator/modes/messages.json
#. [src.components.Navigator.modes.createProduct] - button
#. defaultMessage is:
#. Create Product
msgctxt "dialog header"
msgid "Create Product"
msgstr ""
#: build/locale/src/products/components/ProductListPage/ProductListPage.json
#. [src.products.components.ProductListPage.1542417144] - button
#. defaultMessage is:
@ -2427,6 +2455,14 @@ msgctxt "button"
msgid "Create Product"
msgstr ""
#: build/locale/src/orders/components/OrderProductAddDialog/OrderProductAddDialog.json
#. [src.orders.components.OrderProductAddDialog.1542417144] - dialog header
#. defaultMessage is:
#. Create Product
msgctxt "dialog header"
msgid "Create Product"
msgstr ""
#: build/locale/src/products/views/ProductCreate.json
#. [src.products.views.1542417144] - window title
#. defaultMessage is:
@ -2499,6 +2535,14 @@ msgctxt "header"
msgid "Create Variant"
msgstr ""
#: build/locale/src/components/Navigator/modes/messages.json
#. [src.components.Navigator.modes.createVoucher] - button
#. defaultMessage is:
#. Create Voucher
msgctxt "button"
msgid "Create Voucher"
msgstr ""
#: build/locale/src/discounts/components/VoucherCreatePage/VoucherCreatePage.json
#. [src.discounts.components.VoucherCreatePage.1357216572] - page header
#. defaultMessage is:
@ -2571,6 +2615,14 @@ msgctxt "window title"
msgid "Create collection"
msgstr ""
#: build/locale/src/customers/components/CustomerListPage/CustomerListPage.json
#. [src.customers.components.CustomerListPage.2859116187] - button
#. defaultMessage is:
#. Create customer
msgctxt "button"
msgid "Create customer"
msgstr ""
#: build/locale/src/customers/views/CustomerCreate.json
#. [src.customers.views.2859116187] - window title
#. defaultMessage is:
@ -3451,6 +3503,14 @@ msgctxt "description"
msgid "Discounts"
msgstr ""
#: build/locale/src/components/Navigator/modes/messages.json
#. [src.components.Navigator.modes.helpMode] - navigator help mode description
#. defaultMessage is:
#. Display Help
msgctxt "navigator help mode description"
msgid "Display Help"
msgstr ""
#: build/locale/src/taxes/components/CountryTaxesPage/CountryTaxesPage.json
#. [src.taxes.components.CountryTaxesPage.3500730003] - tax rate
#. defaultMessage is:
@ -4047,6 +4107,14 @@ msgctxt "button"
msgid "Go back to dashboard"
msgstr ""
#: build/locale/src/components/Navigator/modes/messages.json
#. [src.components.Navigator.modes.goToOrder] - navigator action
#. defaultMessage is:
#. Go to order #{orderNumber}
msgctxt "navigator action"
msgid "Go to order #{orderNumber}"
msgstr ""
#: build/locale/src/home/components/HomeHeader/HomeHeader.json
#. [homeHeaderText] - header
#. defaultMessage is:
@ -4811,6 +4879,14 @@ msgctxt "description"
msgid "Name of your store is shown on tab in web browser"
msgstr ""
#: build/locale/src/components/Navigator/Navigator.json
#. [src.components.Navigator.3060198201] - navigator section header
#. defaultMessage is:
#. Navigate to
msgctxt "navigator section header"
msgid "Navigate to"
msgstr ""
#: build/locale/src/intl.json
#. [src.navigation] - navigation section name
#. defaultMessage is:
@ -4819,6 +4895,14 @@ msgctxt "navigation section name"
msgid "Navigation"
msgstr ""
#: build/locale/src/components/Navigator/Navigator.json
#. [src.components.Navigator.4290208300] - navigator notification title
#. defaultMessage is:
#. Navigator is here to help
msgctxt "navigator notification title"
msgid "Navigator is here to help"
msgstr ""
#: build/locale/src/auth/components/NewPasswordPage/NewPasswordPage.json
#. [src.auth.components.NewPasswordPage.1254879564]
#. defaultMessage is:
@ -4875,6 +4959,14 @@ msgctxt "description"
msgid "No Products added to Order"
msgstr ""
#: build/locale/src/components/Navigator/modes/messages.json
#. [src.components.Navigator.modes.noResults]
#. defaultMessage is:
#. No Results
msgctxt "description"
msgid "No Results"
msgstr ""
#: build/locale/src/home/components/HomeActivityCard/HomeActivityCard.json
#. [homeActivityCardNoActivities]
#. defaultMessage is:
@ -5315,6 +5407,14 @@ msgctxt "voucher has no requirements"
msgid "None"
msgstr ""
#: build/locale/src/components/Navigator/modes/messages.json
#. [src.components.Navigator.modes.collectionUnpublished] - collection
#. defaultMessage is:
#. Not Published
msgctxt "collection"
msgid "Not Published"
msgstr ""
#: build/locale/src/pages/components/PageList/PageList.json
#. [src.pages.components.PageList.3767550649] - page status
#. defaultMessage is:
@ -5511,6 +5611,14 @@ msgctxt "description"
msgid "Order History"
msgstr ""
#: build/locale/src/components/Navigator/NavigatorInput.json
#. [src.components.Navigator.1116468870] - navigator placeholder
#. defaultMessage is:
#. Order Number
msgctxt "navigator placeholder"
msgid "Order Number"
msgstr ""
#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json
#. [src.orders.components.OrderListFilter.2222765704]
#. defaultMessage is:
@ -5715,6 +5823,14 @@ msgctxt "description"
msgid "Original String"
msgstr ""
#: build/locale/src/components/Navigator/Navigator.json
#. [src.components.Navigator.3384551821] - navigator notification
#. defaultMessage is:
#. Our new feature to help you with your daily tasks. Run Navigator using {keyboardShortcut} shortcut.
msgctxt "navigator notification"
msgid "Our new feature to help you with your daily tasks. Run Navigator using {keyboardShortcut} shortcut."
msgstr ""
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.1640493122] - product status
#. defaultMessage is:
@ -6251,6 +6367,10 @@ msgctxt "page header"
msgid "Primary Address"
msgstr ""
#: build/locale/src/components/Navigator/modes/messages.json
#. [src.components.Navigator.modes.product]
#. defaultMessage is:
#. Product
#: build/locale/src/orders/components/OrderDraftDetailsProducts/OrderDraftDetailsProducts.json
#. [src.orders.components.OrderDraftDetailsProducts.1895667608]
#. defaultMessage is:
@ -6599,6 +6719,14 @@ msgctxt "product is published"
msgid "Published"
msgstr ""
#: build/locale/src/components/Navigator/modes/messages.json
#. [src.components.Navigator.modes.collectionPublished] - collection
#. defaultMessage is:
#. Published
msgctxt "collection"
msgid "Published"
msgstr ""
#: build/locale/src/pages/components/PageList/PageList.json
#. [src.pages.components.PageList.3640454975] - page status
#. defaultMessage is:
@ -6655,6 +6783,14 @@ msgctxt "ordered products"
msgid "Quantity"
msgstr ""
#: build/locale/src/components/Navigator/Navigator.json
#. [src.components.Navigator.3636839115] - navigator section header
#. defaultMessage is:
#. Quick Actions
msgctxt "navigator section header"
msgid "Quick Actions"
msgstr ""
#: build/locale/src/shipping/components/ShippingZoneCountriesAssignDialog/ShippingZoneCountriesAssignDialog.json
#. [src.shipping.components.ShippingZoneCountriesAssignDialog.1440682557]
#. defaultMessage is:
@ -7043,6 +7179,14 @@ msgctxt "description"
msgid "Search Collection"
msgstr ""
#: build/locale/src/components/Navigator/modes/messages.json
#. [src.components.Navigator.modes.helpCommandsMode] - navigator command mode description
#. defaultMessage is:
#. Search Command
msgctxt "navigator command mode description"
msgid "Search Command"
msgstr ""
#: build/locale/src/shipping/components/ShippingZoneCountriesAssignDialog/ShippingZoneCountriesAssignDialog.json
#. [src.shipping.components.ShippingZoneCountriesAssignDialog.3510295703]
#. defaultMessage is:
@ -7051,6 +7195,14 @@ msgctxt "description"
msgid "Search Countries"
msgstr ""
#: build/locale/src/components/Navigator/NavigatorInput.json
#. [src.components.Navigator.1643417013] - navigator placeholder
#. defaultMessage is:
#. Search Customer
msgctxt "navigator placeholder"
msgid "Search Customer"
msgstr ""
#: build/locale/src/customers/components/CustomerListPage/CustomerListPage.json
#. [src.customers.components.CustomerListPage.1643417013]
#. defaultMessage is:
@ -7059,6 +7211,14 @@ msgctxt "description"
msgid "Search Customer"
msgstr ""
#: build/locale/src/components/Navigator/modes/messages.json
#. [src.components.Navigator.modes.helpCustomersMode] - navigator customer mode description
#. defaultMessage is:
#. Search Customers
msgctxt "navigator customer mode description"
msgid "Search Customers"
msgstr ""
#: build/locale/src/orders/components/OrderCustomer/OrderCustomer.json
#. [src.orders.components.OrderCustomer.2433460203]
#. defaultMessage is:
@ -7147,6 +7307,14 @@ msgctxt "save search tab"
msgid "Search Name"
msgstr ""
#: build/locale/src/components/Navigator/modes/messages.json
#. [src.components.Navigator.modes.helpOrdersMode] - navigator order mode description
#. defaultMessage is:
#. Search Orders
msgctxt "navigator order mode description"
msgid "Search Orders"
msgstr ""
#: build/locale/src/orders/components/OrderListPage/OrderListPage.json
#. [src.orders.components.OrderListPage.355376157]
#. defaultMessage is:
@ -7231,6 +7399,14 @@ msgctxt "description"
msgid "Search Staff Member"
msgstr ""
#: build/locale/src/components/Navigator/modes/messages.json
#. [src.components.Navigator.modes.helpDefaultMode] - navigator default mode description
#. defaultMessage is:
#. Search Views and Actions
msgctxt "navigator default mode description"
msgid "Search Views and Actions"
msgstr ""
#: build/locale/src/discounts/components/VoucherListPage/VoucherListPage.json
#. [src.discounts.components.VoucherListPage.1930485532]
#. defaultMessage is:
@ -7319,6 +7495,38 @@ msgctxt "description"
msgid "Search engine title"
msgstr ""
#: build/locale/src/components/Navigator/modes/messages.json
#. [src.components.Navigator.modes.helpCatalogMode] - navigator catalog mode description
#. defaultMessage is:
#. Search in Catalog
msgctxt "navigator catalog mode description"
msgid "Search in Catalog"
msgstr ""
#: build/locale/src/components/Navigator/Navigator.json
#. [src.components.Navigator.2935523260] - navigator section header
#. defaultMessage is:
#. Search in Catalog
msgctxt "navigator section header"
msgid "Search in Catalog"
msgstr ""
#: build/locale/src/components/Navigator/NavigatorInput.json
#. [src.components.Navigator.2935523260] - navigator placeholder
#. defaultMessage is:
#. Search in Catalog
msgctxt "navigator placeholder"
msgid "Search in Catalog"
msgstr ""
#: build/locale/src/components/Navigator/Navigator.json
#. [src.components.Navigator.1809988825] - navigator section header
#. defaultMessage is:
#. Search in Customers
msgctxt "navigator section header"
msgid "Search in Customers"
msgstr ""
#: build/locale/src/attributes/components/AttributeList/AttributeList.json
#. [src.attributes.components.AttributeList.2235596452] - attribute can be searched in dashboard
#. defaultMessage is:
@ -8603,6 +8811,14 @@ msgctxt "product type is either simple or configurable"
msgid "Type"
msgstr ""
#: build/locale/src/components/Navigator/NavigatorInput.json
#. [src.components.Navigator.1167695965] - navigator placeholder
#. defaultMessage is:
#. Type Command
msgctxt "navigator placeholder"
msgid "Type Command"
msgstr ""
#: build/locale/src/productTypes/components/ProductTypeList/ProductTypeList.json
#. [src.productTypes.components.ProductTypeList.2253986440] - product type name
#. defaultMessage is:
@ -8611,6 +8827,14 @@ msgctxt "product type name"
msgid "Type Name"
msgstr ""
#: build/locale/src/components/Navigator/NavigatorInput.json
#. [src.components.Navigator.2874620973] - navigator placeholder
#. defaultMessage is:
#. Type {key} to see available actions
msgctxt "navigator placeholder"
msgid "Type {key} to see available actions"
msgstr ""
#: build/locale/src/pages/components/PageSlug/PageSlug.json
#. [src.pages.components.PageSlug.1324178587]
#. defaultMessage is:
@ -9715,6 +9939,14 @@ msgctxt "translation progress"
msgid "{current} of {max}"
msgstr ""
#: build/locale/src/components/Navigator/modes/messages.json
#. [src.components.Navigator.modes.customerWithName]
#. defaultMessage is:
#. {firstName} {lastName}
msgctxt "description"
msgid "{firstName} {lastName}"
msgstr ""
#: build/locale/src/components/MoneyRange/MoneyRange.json
#. [src.components.MoneyRange.1316359951] - money
#. defaultMessage is:

13
package-lock.json generated
View file

@ -3286,6 +3286,11 @@
"integrity": "sha512-YesPanU1+WCigC/Aj1Mga8UCOjHIfMNHZ3zzDsUY7lI8GlKnh/Kv2QwJOQ+jNQ36Ru7IfzSedlG14hppYaN13A==",
"dev": true
},
"@types/semver-compare": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/semver-compare/-/semver-compare-1.0.1.tgz",
"integrity": "sha512-wx2LQVvKlEkhXp/HoKIZ/aSL+TvfJdKco8i0xJS3aR877mg4qBHzNT6+B5a61vewZHo79EdZavskGnRXEC2H6A=="
},
"@types/shallowequal": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/shallowequal/-/shallowequal-1.1.1.tgz",
@ -11333,6 +11338,11 @@
"integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==",
"dev": true
},
"hotkeys-js": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/hotkeys-js/-/hotkeys-js-3.7.2.tgz",
"integrity": "sha512-LJIPBgejlklphThig2edlYUiPq3iFDHdsXftvZ0VWrpi330dRwD2YxPlzOYv0UQEatvZdgwG3ZLCfJ020ix8vQ=="
},
"hpack.js": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
@ -18050,8 +18060,7 @@
"semver-compare": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
"integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=",
"dev": true
"integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w="
},
"send": {
"version": "0.17.1",

View file

@ -39,6 +39,7 @@
"fuzzaldrin": "^2.1.0",
"graphql": "^14.4.2",
"graphql-tag": "^2.10.1",
"hotkeys-js": "^3.7.2",
"is-url": "^1.2.4",
"jss": "^9.8.7",
"keycode": "^2.2.0",
@ -62,6 +63,7 @@
"react-sortable-hoc": "^0.6.8",
"react-sortable-tree": "^2.6.2",
"react-svg": "^2.2.11",
"semver-compare": "^1.0.0",
"slugify": "^1.3.4",
"typescript": "^3.6.4",
"url-join": "^4.0.1",
@ -97,6 +99,7 @@
"@types/react-sortable-hoc": "^0.6.5",
"@types/react-sortable-tree": "^0.3.6",
"@types/react-test-renderer": "^16.8.2",
"@types/semver-compare": "^1.0.1",
"@types/storybook__addon-storyshots": "^3.4.9",
"@types/storybook__react": "^4.0.2",
"@types/url-join": "^0.8.3",

View file

@ -0,0 +1,206 @@
import Fade from "@material-ui/core/Fade";
import Modal from "@material-ui/core/Modal";
import Paper from "@material-ui/core/Paper";
import makeStyles from "@material-ui/core/styles/makeStyles";
import useTheme from "@material-ui/core/styles/useTheme";
import Downshift from "downshift";
import hotkeys from "hotkeys-js";
import React from "react";
import { useIntl } from "react-intl";
import cmp from "semver-compare";
import { APP_VERSION } from "@saleor/config";
import useLocalStorage from "@saleor/hooks/useLocalStorage";
import useNotifier from "@saleor/hooks/useNotifier";
import {
getActions,
getCatalog,
getCustomers,
getViews,
hasActions,
hasCatalog,
hasCustomers,
hasViews
} from "./modes/utils";
import NavigatorInput from "./NavigatorInput";
import NavigatorSection from "./NavigatorSection";
import { QuickSearchAction } from "./types";
import useQuickSearch from "./useQuickSearch";
const navigatorHotkey = "ctrl+k, command+k";
const navigatorNotificationStorageKey = "notifiedAboutNavigator";
function getItemOffset(
actions: QuickSearchAction[],
cbs: Array<typeof getViews>
): number {
return cbs.reduce((acc, cb) => cb(actions).length + acc, 0);
}
const useStyles = makeStyles(
theme => ({
modal: {
alignItems: "center",
display: "flex",
justifyContent: "center",
padding: theme.spacing(3)
},
paper: {
overflow: "hidden"
},
root: {
height: 500,
maxWidth: 600,
outline: 0,
width: "100%"
}
}),
{
name: "Navigator"
}
);
const Navigator: React.FC = () => {
const [visible, setVisible] = React.useState(false);
const input = React.useRef(null);
const [query, mode, change, actions] = useQuickSearch(visible, input);
const intl = useIntl();
const notify = useNotifier();
const [notifiedAboutNavigator, setNotifiedAboutNavigator] = useLocalStorage(
navigatorNotificationStorageKey,
false
);
const classes = useStyles({});
const theme = useTheme();
React.useEffect(() => {
hotkeys(navigatorHotkey, event => {
event.preventDefault();
setVisible(!visible);
});
if (cmp(APP_VERSION, "2.1.0") !== 1 && !notifiedAboutNavigator) {
notify({
autohide: null,
text: intl.formatMessage(
{
defaultMessage:
"Our new feature to help you with your daily tasks. Run Navigator using {keyboardShortcut} shortcut.",
description: "navigator notification"
},
{
keyboardShortcut:
navigator.platform.toLowerCase().indexOf("mac") >= 0
? "⌘+K"
: "Ctrl+K"
}
),
title: intl.formatMessage({
defaultMessage: "Navigator is here to help",
description: "navigator notification title"
})
});
setNotifiedAboutNavigator(true);
}
return () => hotkeys.unbind(navigatorHotkey);
}, []);
return (
<Modal
className={classes.modal}
open={visible}
onClose={() => setVisible(false)}
>
<Fade appear in={visible} timeout={theme.transitions.duration.short}>
<div className={classes.root}>
<Paper className={classes.paper}>
<Downshift
itemToString={(item: QuickSearchAction) =>
item ? item.label : ""
}
onSelect={(item: QuickSearchAction) => {
const shouldRemainVisible = item.onClick();
if (!shouldRemainVisible) {
setVisible(false);
}
}}
onInputValueChange={value =>
change({
target: {
name: "query",
value
}
})
}
defaultHighlightedIndex={0}
>
{({ getInputProps, getItemProps, highlightedIndex }) => (
<div>
<NavigatorInput
mode={mode}
value={query}
{...getInputProps({
value: query
})}
ref={input}
/>
{hasViews(actions) && (
<NavigatorSection
label={intl.formatMessage({
defaultMessage: "Navigate to",
description: "navigator section header"
})}
getItemProps={getItemProps}
highlightedIndex={highlightedIndex}
items={getViews(actions)}
offset={0}
/>
)}
{hasActions(actions) && (
<NavigatorSection
label={intl.formatMessage({
defaultMessage: "Quick Actions",
description: "navigator section header"
})}
getItemProps={getItemProps}
highlightedIndex={highlightedIndex}
items={getActions(actions)}
offset={getItemOffset(actions, [getViews])}
/>
)}
{hasCustomers(actions) && (
<NavigatorSection
label={intl.formatMessage({
defaultMessage: "Search in Customers",
description: "navigator section header"
})}
getItemProps={getItemProps}
highlightedIndex={highlightedIndex}
items={getCustomers(actions)}
offset={getItemOffset(actions, [getViews, getActions])}
/>
)}
{hasCatalog(actions) && (
<NavigatorSection
label={intl.formatMessage({
defaultMessage: "Search in Catalog",
description: "navigator section header"
})}
getItemProps={getItemProps}
highlightedIndex={highlightedIndex}
items={getCatalog(actions)}
offset={0}
/>
)}
</div>
)}
</Downshift>
</Paper>
</div>
</Fade>
</Modal>
);
};
export default Navigator;

View file

@ -0,0 +1,112 @@
import makeStyles from "@material-ui/core/styles/makeStyles";
import React from "react";
import { useIntl } from "react-intl";
import { QuickSearchMode } from "./types";
const useStyles = makeStyles(
theme => {
const typography = {
color: theme.palette.text.primary,
fontSize: 24,
lineHeight: 1.33
};
return {
adornment: {
...typography,
color: theme.palette.text.secondary,
paddingRight: theme.spacing(1)
},
input: {
...typography,
background: "transparent",
border: "none",
outline: 0,
padding: 0,
width: "100%"
},
root: {
background: theme.palette.background.default,
display: "flex",
padding: theme.spacing(2, 3)
}
};
},
{
name: "NavigatorInput"
}
);
interface NavigatorInputProps
extends React.InputHTMLAttributes<HTMLInputElement> {
mode: QuickSearchMode;
}
const NavigatorInput = React.forwardRef<HTMLInputElement, NavigatorInputProps>(
(props, ref) => {
const { mode, ...rest } = props;
const classes = useStyles(props);
const intl = useIntl();
return (
<div className={classes.root}>
{mode !== "default" && (
<span className={classes.adornment}>
{mode === "orders"
? "#"
: mode === "customers"
? "@"
: mode === "catalog"
? "$"
: mode === "help"
? "?"
: ">"}
</span>
)}
<input
autoFocus
autoComplete="off"
className={classes.input}
placeholder={
mode === "orders"
? intl.formatMessage({
defaultMessage: "Order Number",
description: "navigator placeholder"
})
: mode === "commands"
? intl.formatMessage({
defaultMessage: "Type Command",
description: "navigator placeholder"
})
: mode === "catalog"
? intl.formatMessage({
defaultMessage: "Search in Catalog",
description: "navigator placeholder"
})
: mode === "customers"
? intl.formatMessage({
defaultMessage: "Search Customer",
description: "navigator placeholder"
})
: mode === "default"
? intl.formatMessage(
{
defaultMessage: "Type {key} to see available actions",
description: "navigator placeholder"
},
{
key: "'?'"
}
)
: null
}
ref={ref}
{...rest}
/>
</div>
);
}
);
NavigatorInput.displayName = "NavigatorInput";
export default NavigatorInput;

View file

@ -0,0 +1,104 @@
import MenuItem from "@material-ui/core/MenuItem";
import makeStyles from "@material-ui/core/styles/makeStyles";
import Typography from "@material-ui/core/Typography";
import { GetItemPropsOptions } from "downshift";
import React from "react";
import Hr from "../Hr";
import { QuickSearchAction } from "./types";
interface NavigatorSectionProps {
getItemProps: (options: GetItemPropsOptions) => any;
highlightedIndex: number;
label: string;
items: QuickSearchAction[];
offset: number;
}
const useStyles = makeStyles(
theme => ({
item: {
"&&&&": {
color: theme.palette.text.secondary,
fontWeight: 400
},
display: "flex",
margin: theme.spacing(1, 0)
},
itemLabel: {
display: "inline-block"
},
label: {
paddingLeft: theme.spacing(2),
textTransform: "uppercase"
},
root: {
"&:last-child": {
marginBottom: 0
},
margin: theme.spacing(2, 0),
padding: theme.spacing(0, 1)
},
spacer: {
flex: 1
},
symbol: {
display: "inline-block",
fontWeight: 600,
width: theme.spacing(4)
}
}),
{
name: "NavigatorSection"
}
);
const NavigatorSection: React.FC<NavigatorSectionProps> = props => {
const { getItemProps, highlightedIndex, label, items, offset } = props;
const classes = useStyles(props);
return (
<div className={classes.root}>
<Typography
className={classes.label}
variant="body2"
color="textSecondary"
>
{label}
</Typography>
<Hr />
{items.map((item, itemIndex) => {
const index = offset + itemIndex;
const itemProps = getItemProps({
index,
item
});
return (
<MenuItem
{...itemProps}
className={classes.item}
selected={highlightedIndex === index}
key={[item.label, item.type].join(":")}
>
<span className={classes.itemLabel}>
{item.symbol && (
<span className={classes.symbol}>{item.symbol}</span>
)}
<span>{item.label}</span>
{item.caption && (
<Typography variant="caption">{item.caption}</Typography>
)}
</span>
<span className={classes.spacer} />
{item.extraInfo}
</MenuItem>
);
})}
</div>
);
};
NavigatorSection.displayName = "NavigatorSection";
export default NavigatorSection;

View file

@ -0,0 +1,2 @@
export { default } from "./Navigator";
export * from "./Navigator";

View file

@ -0,0 +1,102 @@
import { score } from "fuzzaldrin";
import { IntlShape } from "react-intl";
import { categoryUrl } from "@saleor/categories/urls";
import { collectionUrl } from "@saleor/collections/urls";
import { UseNavigatorResult } from "@saleor/hooks/useNavigator";
import { maybe } from "@saleor/misc";
import { productUrl } from "@saleor/products/urls";
import { SearchCatalog } from "../queries/types/SearchCatalog";
import { QuickSearchAction, QuickSearchActionInput } from "../types";
import messages from "./messages";
import { sortScores } from "./utils";
const maxActions = 5;
export function searchInCatalog(
search: string,
intl: IntlShape,
navigate: UseNavigatorResult,
catalog: SearchCatalog
): QuickSearchAction[] {
const categories: QuickSearchActionInput[] = maybe(
() => catalog.categories.edges.map(edge => edge.node),
[]
)
.map<QuickSearchActionInput>(category => ({
caption: intl.formatMessage(messages.category),
label: category.name,
onClick: () => {
navigate(categoryUrl(category.id));
return false;
},
score: score(category.name, search),
text: category.name,
type: "catalog"
}))
.sort(sortScores);
const collections: QuickSearchActionInput[] = maybe(
() => catalog.collections.edges.map(edge => edge.node),
[]
)
.map<QuickSearchActionInput>(collection => ({
caption: intl.formatMessage(messages.collection),
extraInfo: intl.formatMessage(
collection.isPublished
? messages.collectionPublished
: messages.collectionUnpublished
),
label: collection.name,
onClick: () => {
navigate(collectionUrl(collection.id));
return false;
},
score: score(collection.name, search),
text: collection.name,
type: "catalog"
}))
.sort(sortScores);
const products: QuickSearchActionInput[] = maybe(
() => catalog.products.edges.map(edge => edge.node),
[]
)
.map<QuickSearchActionInput>(product => ({
caption: intl.formatMessage(messages.product),
extraInfo: product.category.name,
label: product.name,
onClick: () => {
navigate(productUrl(product.id));
return false;
},
score: score(product.name, search),
text: product.name,
type: "catalog"
}))
.sort(sortScores);
const baseActions = [
...categories.slice(0, 1),
...collections.slice(0, 1),
...products.slice(0, 1)
];
return [
...baseActions,
...[...categories.slice(1), ...collections.slice(1), ...products.slice(1)]
.sort(sortScores)
.slice(0, maxActions - baseActions.length)
].sort(sortScores);
}
function getCatalogModeActions(
query: string,
intl: IntlShape,
navigate: UseNavigatorResult,
catalog: SearchCatalog
): QuickSearchAction[] {
return searchInCatalog(query, intl, navigate, catalog);
}
export default getCatalogModeActions;

View file

@ -0,0 +1,104 @@
import { score } from "fuzzaldrin";
import { IntlShape } from "react-intl";
import { categoryAddUrl } from "@saleor/categories/urls";
import { collectionAddUrl } from "@saleor/collections/urls";
import { customerAddUrl } from "@saleor/customers/urls";
import { voucherAddUrl } from "@saleor/discounts/urls";
import { UseNavigatorResult } from "@saleor/hooks/useNavigator";
import { OrderDraftCreate } from "@saleor/orders/types/OrderDraftCreate";
import { productAddUrl } from "@saleor/products/urls";
import { MutationFunction } from "react-apollo";
import { QuickSearchActionInput, QuickSearchMode } from "../../types";
import messages from "../messages";
import { sortScores } from "../utils";
const threshold = 0.05;
const maxActions = 5;
interface Command {
label: string;
onClick: () => boolean;
}
export function searchInCommands(
search: string,
intl: IntlShape,
navigate: UseNavigatorResult,
createOrder: MutationFunction<OrderDraftCreate, {}>,
setMode: (mode: QuickSearchMode) => void
): QuickSearchActionInput[] {
const actions: Command[] = [
{
label: intl.formatMessage(messages.createCategory),
onClick: () => {
navigate(categoryAddUrl());
return false;
}
},
{
label: intl.formatMessage(messages.createCollection),
onClick: () => {
navigate(collectionAddUrl);
return false;
}
},
{
label: intl.formatMessage(messages.createProduct),
onClick: () => {
navigate(productAddUrl);
return false;
}
},
{
label: intl.formatMessage(messages.createCustomer),
onClick: () => {
navigate(customerAddUrl);
return false;
}
},
{
label: intl.formatMessage(messages.createVoucher),
onClick: () => {
navigate(voucherAddUrl);
return false;
}
},
{
label: intl.formatMessage(messages.createOrder),
onClick: () => {
createOrder();
return false;
}
},
{
label: intl.formatMessage(messages.helpMode),
onClick: () => {
setMode("help");
return true;
}
}
];
return actions.map(action => ({
label: action.label,
onClick: action.onClick,
score: score(action.label, search),
text: action.label,
type: "action"
}));
}
function getCommandModeActions(
query: string,
intl: IntlShape,
navigate: UseNavigatorResult,
createOrder: MutationFunction<OrderDraftCreate, {}>,
setMode: (mode: QuickSearchMode) => void
): QuickSearchActionInput[] {
return [...searchInCommands(query, intl, navigate, createOrder, setMode)]
.filter(action => action.score >= threshold)
.sort(sortScores)
.slice(0, maxActions);
}
export default getCommandModeActions;

View file

@ -0,0 +1,2 @@
export * from "./actions";
export { default } from "./actions";

View file

@ -0,0 +1,40 @@
import { IntlShape } from "react-intl";
import { customerUrl } from "@saleor/customers/urls";
import { UseNavigatorResult } from "@saleor/hooks/useNavigator";
import { SearchCustomers_search_edges_node } from "@saleor/searches/types/SearchCustomers";
import { QuickSearchAction } from "../types";
import messages from "./messages";
export function searchInCustomers(
intl: IntlShape,
navigate: UseNavigatorResult,
customers: SearchCustomers_search_edges_node[]
): QuickSearchAction[] {
return customers.map(customer => ({
caption: customer.email,
label:
customer.firstName && customer.lastName
? intl.formatMessage(messages.customerWithName, {
firstName: customer.firstName,
lastName: customer.lastName
})
: customer.email,
onClick: () => {
navigate(customerUrl(customer.id));
return false;
},
score: 1,
type: "customer"
}));
}
function getCustomersModeActions(
intl: IntlShape,
navigate: UseNavigatorResult,
customers: SearchCustomers_search_edges_node[]
): QuickSearchAction[] {
return searchInCustomers(intl, navigate, customers);
}
export default getCustomersModeActions;

View file

@ -0,0 +1,30 @@
import { MutationFunction } from "react-apollo";
import { IntlShape } from "react-intl";
import { UseNavigatorResult } from "@saleor/hooks/useNavigator";
import { OrderDraftCreate } from "@saleor/orders/types/OrderDraftCreate";
import { QuickSearchAction, QuickSearchMode } from "../../types";
import { searchInCommands } from "../commands";
import { sortScores } from "../utils";
import searchInViews from "./views";
const threshold = 0.05;
const maxActions = 5;
function getDefaultModeActions(
query: string,
intl: IntlShape,
navigate: UseNavigatorResult,
createOrder: MutationFunction<OrderDraftCreate, {}>,
setMode: (mode: QuickSearchMode) => void
): QuickSearchAction[] {
return [
...searchInViews(query, intl, navigate),
...searchInCommands(query, intl, navigate, createOrder, setMode)
]
.filter(action => action.score >= threshold)
.sort(sortScores)
.slice(0, maxActions);
}
export default getDefaultModeActions;

View file

@ -0,0 +1,3 @@
export * from "./default";
export { default } from "./default";
export * from "./views";

View file

@ -0,0 +1,134 @@
import { score } from "fuzzaldrin";
import { IntlShape } from "react-intl";
import { attributeListUrl } from "@saleor/attributes/urls";
import { categoryListUrl } from "@saleor/categories/urls";
import { collectionListUrl } from "@saleor/collections/urls";
import { customerListUrl } from "@saleor/customers/urls";
import { saleListUrl, voucherListUrl } from "@saleor/discounts/urls";
import { UseNavigatorResult } from "@saleor/hooks/useNavigator";
import { sectionNames } from "@saleor/intl";
import { menuListUrl } from "@saleor/navigation/urls";
import { orderDraftListUrl, orderListUrl } from "@saleor/orders/urls";
import { pageListUrl } from "@saleor/pages/urls";
import { pluginsListUrl } from "@saleor/plugins/urls";
import { productListUrl } from "@saleor/products/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";
import { countryListUrl } from "@saleor/taxes/urls";
import { languageListUrl } from "@saleor/translations/urls";
import { webhooksListUrl } from "@saleor/webhooks/urls";
import { QuickSearchActionInput } from "../../types";
interface View {
label: string;
url: string;
}
function searchInViews(
search: string,
intl: IntlShape,
navigate: UseNavigatorResult
): QuickSearchActionInput[] {
const views: View[] = [
{
label: intl.formatMessage(sectionNames.attributes),
url: attributeListUrl()
},
{
label: intl.formatMessage(sectionNames.categories),
url: categoryListUrl()
},
{
label: intl.formatMessage(sectionNames.collections),
url: collectionListUrl()
},
{
label: intl.formatMessage(sectionNames.customers),
url: customerListUrl()
},
{
label: intl.formatMessage(sectionNames.draftOrders),
url: orderDraftListUrl()
},
{
label: intl.formatMessage(sectionNames.home),
url: "/"
},
{
label: intl.formatMessage(sectionNames.navigation),
url: menuListUrl()
},
{
label: intl.formatMessage(sectionNames.orders),
url: orderListUrl()
},
{
label: intl.formatMessage(sectionNames.pages),
url: pageListUrl()
},
{
label: intl.formatMessage(sectionNames.plugins),
url: pluginsListUrl()
},
{
label: intl.formatMessage(sectionNames.productTypes),
url: productTypeListUrl()
},
{
label: intl.formatMessage(sectionNames.products),
url: productListUrl()
},
{
label: intl.formatMessage(sectionNames.sales),
url: saleListUrl()
},
{
label: intl.formatMessage(sectionNames.serviceAccounts),
url: serviceListUrl()
},
{
label: intl.formatMessage(sectionNames.shipping),
url: shippingZonesListUrl()
},
{
label: intl.formatMessage(sectionNames.siteSettings),
url: siteSettingsUrl()
},
{
label: intl.formatMessage(sectionNames.staff),
url: staffListUrl()
},
{
label: intl.formatMessage(sectionNames.taxes),
url: countryListUrl
},
{
label: intl.formatMessage(sectionNames.translations),
url: languageListUrl
},
{
label: intl.formatMessage(sectionNames.vouchers),
url: voucherListUrl()
},
{
label: intl.formatMessage(sectionNames.webhooks),
url: webhooksListUrl()
}
];
return views.map(view => ({
label: view.label,
onClick: () => {
navigate(view.url);
return false;
},
score: score(view.label, search),
text: view.label,
type: "view"
}));
}
export default searchInViews;

View file

@ -0,0 +1,79 @@
import { IntlShape } from "react-intl";
import { QuickSearchAction, QuickSearchMode } from "../types";
import messages from "./messages";
function getHelpModeActions(
query: string,
intl: IntlShape,
setMode: (mode: QuickSearchMode) => void
): QuickSearchAction[] {
if (query !== "") {
return [
{
label: intl.formatMessage(messages.noResults),
onClick: () => true,
type: "action"
}
];
}
return [
{
label: intl.formatMessage(messages.helpDefaultMode),
onClick: () => {
setMode("default");
return true;
},
symbol: "...",
type: "action"
},
{
label: intl.formatMessage(messages.helpCommandsMode),
onClick: () => {
setMode("commands");
return true;
},
symbol: ">",
type: "action"
},
{
label: intl.formatMessage(messages.helpOrdersMode),
onClick: () => {
setMode("orders");
return true;
},
symbol: "#",
type: "action"
},
{
label: intl.formatMessage(messages.helpCustomersMode),
onClick: () => {
setMode("customers");
return true;
},
symbol: "@",
type: "action"
},
{
label: intl.formatMessage(messages.helpCatalogMode),
onClick: () => {
setMode("catalog");
return true;
},
symbol: "$",
type: "action"
},
{
label: intl.formatMessage(messages.helpMode),
onClick: () => {
setMode("help");
return true;
},
symbol: "?",
type: "action"
}
];
}
export default getHelpModeActions;

View file

@ -0,0 +1,54 @@
import { IntlShape } from "react-intl";
import { UseNavigatorResult } from "@saleor/hooks/useNavigator";
import { OrderDraftCreate } from "@saleor/orders/types/OrderDraftCreate";
import { MutationFunction } from "react-apollo";
import { QuickSearchAction, QuickSearchMode } from "../types";
import getCatalogModeActions from "./catalog";
import getCommandModeActions from "./commands";
import getCustomersModeActions from "./customers";
import getDefaultModeActions from "./default";
import getHelpModeActions from "./help";
import getOrdersModeActions from "./orders";
import { ActionQueries } from "./types";
function getModeActions(
mode: QuickSearchMode,
query: string,
intl: IntlShape,
queries: ActionQueries,
cbs: {
createOrder: MutationFunction<OrderDraftCreate, {}>;
navigate: UseNavigatorResult;
setMode: (mode: QuickSearchMode) => void;
}
): QuickSearchAction[] {
switch (mode) {
case "catalog":
return getCatalogModeActions(query, intl, cbs.navigate, queries.catalog);
case "commands":
return getCommandModeActions(
query,
intl,
cbs.navigate,
cbs.createOrder,
cbs.setMode
);
case "customers":
return getCustomersModeActions(intl, cbs.navigate, queries.customers);
case "help":
return getHelpModeActions(query, intl, cbs.setMode);
case "orders":
return getOrdersModeActions(query, intl, cbs.navigate, queries.order);
default:
return getDefaultModeActions(
query,
intl,
cbs.navigate,
cbs.createOrder,
cbs.setMode
);
}
}
export default getModeActions;

View file

@ -0,0 +1,81 @@
import { defineMessages } from "react-intl";
const messages = defineMessages({
category: {
defaultMessage: "Category"
},
collection: {
defaultMessage: "Collection"
},
collectionPublished: {
defaultMessage: "Published",
description: "collection"
},
collectionUnpublished: {
defaultMessage: "Not Published",
description: "collection"
},
createCategory: {
defaultMessage: "Create Category",
description: "button"
},
createCollection: {
defaultMessage: "Create Collection",
description: "button"
},
createCustomer: {
defaultMessage: "Create Customer",
description: "button"
},
createOrder: {
defaultMessage: "Create Order",
description: "button"
},
createProduct: {
defaultMessage: "Create Product",
description: "button"
},
createVoucher: {
defaultMessage: "Create Voucher",
description: "button"
},
customerWithName: {
defaultMessage: "{firstName} {lastName}"
},
goToOrder: {
defaultMessage: "Go to order #{orderNumber}",
description: "navigator action"
},
helpCatalogMode: {
defaultMessage: "Search in Catalog",
description: "navigator catalog mode description"
},
helpCommandsMode: {
defaultMessage: "Search Command",
description: "navigator command mode description"
},
helpCustomersMode: {
defaultMessage: "Search Customers",
description: "navigator customer mode description"
},
helpDefaultMode: {
defaultMessage: "Search Views and Actions",
description: "navigator default mode description"
},
helpMode: {
defaultMessage: "Display Help",
description: "navigator help mode description"
},
helpOrdersMode: {
defaultMessage: "Search Orders",
description: "navigator order mode description"
},
noResults: {
defaultMessage: "No Results"
},
product: {
defaultMessage: "Product"
}
});
export default messages;

View file

@ -0,0 +1,45 @@
import { IntlShape } from "react-intl";
import { UseNavigatorResult } from "@saleor/hooks/useNavigator";
import { maybe, transformOrderStatus } from "@saleor/misc";
import { orderUrl } from "@saleor/orders/urls";
import { CheckIfOrderExists_order } from "../queries/types/CheckIfOrderExists";
import { QuickSearchAction } from "../types";
import messages from "./messages";
export function isQueryValidOrderNumber(query: string): boolean {
return query === parseInt(query, 0).toString();
}
export function getGqlOrderId(orderNumber: string): string {
return btoa(`Order:${orderNumber}`);
}
function getOrdersModeActions(
query: string,
intl: IntlShape,
navigate: UseNavigatorResult,
order: CheckIfOrderExists_order
): QuickSearchAction[] {
const gqlId = getGqlOrderId(query);
if (isQueryValidOrderNumber(query) && maybe(() => order.id === gqlId)) {
return [
{
extraInfo: transformOrderStatus(order.status, intl).localized,
label: intl.formatMessage(messages.goToOrder, {
orderNumber: query
}),
onClick: () => {
navigate(orderUrl(gqlId));
return false;
},
type: "action"
}
];
}
return [];
}
export default getOrdersModeActions;

View file

@ -0,0 +1,9 @@
import { SearchCustomers_search_edges_node } from "@saleor/searches/types/SearchCustomers";
import { CheckIfOrderExists_order } from "../queries/types/CheckIfOrderExists";
import { SearchCatalog } from "../queries/types/SearchCatalog";
export interface ActionQueries {
catalog: SearchCatalog;
customers: SearchCustomers_search_edges_node[];
order: CheckIfOrderExists_order;
}

View file

@ -0,0 +1,60 @@
import {
QuickSearchAction,
QuickSearchActionInput,
QuickSearchMode
} from "../types";
export function getActions(actions: QuickSearchAction[]): QuickSearchAction[] {
return actions.filter(action => action.type === "action");
}
export function hasActions(actions: QuickSearchAction[]): boolean {
return getActions(actions).length > 0;
}
export function getViews(actions: QuickSearchAction[]): QuickSearchAction[] {
return actions.filter(action => action.type === "view");
}
export function hasViews(actions: QuickSearchAction[]): boolean {
return getViews(actions).length > 0;
}
export function getCustomers(
actions: QuickSearchAction[]
): QuickSearchAction[] {
return actions.filter(action => action.type === "customer");
}
export function hasCustomers(actions: QuickSearchAction[]): boolean {
return getCustomers(actions).length > 0;
}
export function getCatalog(actions: QuickSearchAction[]): QuickSearchAction[] {
return actions.filter(action => action.type === "catalog");
}
export function hasCatalog(actions: QuickSearchAction[]): boolean {
return getCatalog(actions).length > 0;
}
export function sortScores(
a: QuickSearchActionInput,
b: QuickSearchActionInput
) {
return a.score <= b.score ? 1 : -1;
}
export function getMode(command: string): QuickSearchMode {
switch (command) {
case ">":
return "commands";
case "@":
return "customers";
case "#":
return "orders";
case "$":
return "catalog";
case "?":
return "help";
default:
return null;
}
}

View file

@ -0,0 +1,23 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { OrderStatus } from "./../../../../types/globalTypes";
// ====================================================
// GraphQL query operation: CheckIfOrderExists
// ====================================================
export interface CheckIfOrderExists_order {
__typename: "Order";
id: string;
status: OrderStatus;
}
export interface CheckIfOrderExists {
order: CheckIfOrderExists_order | null;
}
export interface CheckIfOrderExistsVariables {
id: string;
}

View file

@ -0,0 +1,75 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
// ====================================================
// GraphQL query operation: SearchCatalog
// ====================================================
export interface SearchCatalog_categories_edges_node {
__typename: "Category";
id: string;
name: string;
}
export interface SearchCatalog_categories_edges {
__typename: "CategoryCountableEdge";
node: SearchCatalog_categories_edges_node;
}
export interface SearchCatalog_categories {
__typename: "CategoryCountableConnection";
edges: SearchCatalog_categories_edges[];
}
export interface SearchCatalog_collections_edges_node {
__typename: "Collection";
id: string;
name: string;
isPublished: boolean;
publicationDate: any | null;
}
export interface SearchCatalog_collections_edges {
__typename: "CollectionCountableEdge";
node: SearchCatalog_collections_edges_node;
}
export interface SearchCatalog_collections {
__typename: "CollectionCountableConnection";
edges: SearchCatalog_collections_edges[];
}
export interface SearchCatalog_products_edges_node_category {
__typename: "Category";
id: string;
name: string;
}
export interface SearchCatalog_products_edges_node {
__typename: "Product";
id: string;
category: SearchCatalog_products_edges_node_category;
name: string;
}
export interface SearchCatalog_products_edges {
__typename: "ProductCountableEdge";
node: SearchCatalog_products_edges_node;
}
export interface SearchCatalog_products {
__typename: "ProductCountableConnection";
edges: SearchCatalog_products_edges[];
}
export interface SearchCatalog {
categories: SearchCatalog_categories | null;
collections: SearchCatalog_collections | null;
products: SearchCatalog_products | null;
}
export interface SearchCatalogVariables {
first: number;
query: string;
}

View file

@ -0,0 +1,66 @@
import gql from "graphql-tag";
import { useState } from "react";
import makeQuery, { UseQueryResult } from "@saleor/hooks/makeQuery";
import useDebounce from "@saleor/hooks/useDebounce";
import { SearchCatalog, SearchCatalogVariables } from "./types/SearchCatalog";
const searchCatalog = gql`
query SearchCatalog($first: Int!, $query: String!) {
categories(first: $first, filter: { search: $query }) {
edges {
node {
id
name
}
}
}
collections(first: $first, filter: { search: $query }) {
edges {
node {
id
name
isPublished
publicationDate
}
}
}
products(first: $first, filter: { search: $query }) {
edges {
node {
id
category {
id
name
}
name
}
}
}
}
`;
const useSearchCatalogQuery = makeQuery<SearchCatalog, SearchCatalogVariables>(
searchCatalog
);
type UseSearchCatalog = [
UseQueryResult<SearchCatalog, SearchCatalogVariables>,
(query: string) => void
];
function useSearchCatalog(first: number): UseSearchCatalog {
const [query, setQuery] = useState("");
const setQueryDebounced = useDebounce(setQuery);
const result = useSearchCatalogQuery({
skip: query === "",
variables: {
first,
query
}
});
return [result, setQueryDebounced];
}
export default useSearchCatalog;

View file

@ -0,0 +1,41 @@
import gql from "graphql-tag";
import { useState } from "react";
import makeQuery, { UseQueryResult } from "@saleor/hooks/makeQuery";
import useDebounce from "@saleor/hooks/useDebounce";
import {
CheckIfOrderExists,
CheckIfOrderExistsVariables
} from "./types/CheckIfOrderExists";
const checkIfOrderExists = gql`
query CheckIfOrderExists($id: ID!) {
order(id: $id) {
id
status
}
}
`;
const useCheckIfOrderExistsQuery = makeQuery<
CheckIfOrderExists,
CheckIfOrderExistsVariables
>(checkIfOrderExists);
type UseCheckIfOrderExists = [
UseQueryResult<CheckIfOrderExists, CheckIfOrderExistsVariables>,
(query: string) => void
];
function useCheckIfOrderExists(): UseCheckIfOrderExists {
const [id, setId] = useState("");
const setIdDebounced = useDebounce(setId);
const result = useCheckIfOrderExistsQuery({
skip: id === "",
variables: {
id
}
});
return [result, setIdDebounced];
}
export default useCheckIfOrderExists;

View file

@ -0,0 +1,24 @@
export type QuickSearchActionType = "action" | "catalog" | "customer" | "view";
export interface QuickSearchAction {
caption?: string;
extraInfo?: string;
label: string;
price?: number;
symbol?: string;
type: QuickSearchActionType;
onClick: () => boolean;
}
export interface QuickSearchActionInput extends QuickSearchAction {
score: number;
text: string;
}
export type QuickSearchMode =
| "default"
| "catalog"
| "commands"
| "customers"
| "help"
| "orders";

View file

@ -0,0 +1,125 @@
import { RefObject, useEffect, useState } from "react";
import { useIntl } from "react-intl";
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
import { ChangeEvent, FormChange } from "@saleor/hooks/useForm";
import useModalDialogOpen from "@saleor/hooks/useModalDialogOpen";
import useNavigator from "@saleor/hooks/useNavigator";
import { maybe } from "@saleor/misc";
import { useOrderDraftCreateMutation } from "@saleor/orders/mutations";
import { orderUrl } from "@saleor/orders/urls";
import useCustomerSearch from "@saleor/searches/useCustomerSearch";
import getModeActions from "./modes";
import { getGqlOrderId, isQueryValidOrderNumber } from "./modes/orders";
import { getMode } from "./modes/utils";
import useSearchCatalog from "./queries/useCatalogSearch";
import useCheckIfOrderExists from "./queries/useCheckIfOrderExists";
import { QuickSearchAction, QuickSearchMode } from "./types";
type UseQuickSearch = [
string,
QuickSearchMode,
FormChange,
QuickSearchAction[]
];
function useQuickSearch(
open: boolean,
input: RefObject<HTMLInputElement>
): UseQuickSearch {
const [query, setQuery] = useState("");
const [mode, setMode] = useState<QuickSearchMode>("default");
const intl = useIntl();
const navigate = useNavigator();
const [{ data: orderData }, getOrderData] = useCheckIfOrderExists();
const { result: customers, search: searchCustomers } = useCustomerSearch({
variables: {
...DEFAULT_INITIAL_SEARCH_DATA,
first: 5
}
});
const [{ data: catalog }, searchCatalog] = useSearchCatalog(5);
const [createOrder] = useOrderDraftCreateMutation({
onCompleted: result => {
if (result.draftOrderCreate.errors.length === 0) {
navigate(orderUrl(result.draftOrderCreate.order.id));
}
}
});
useModalDialogOpen(open, {
onClose: () => {
setMode("default");
setQuery("");
}
});
const handleBack = (event: KeyboardEvent) => {
// `any` type because of poorly typed `KeyboardEvent.EventTarget` which
// has no `value` key. Which it would have if `KeyboardEvent` and
// `EventTarget` would be generic types accepting HTMLDOM element types.
if ((event.target as any).value === "" && event.keyCode === 8) {
setMode("default");
}
};
useEffect(() => {
setQuery("");
if (mode !== "default" && input.current) {
input.current.addEventListener("keyup", handleBack);
return () => {
if (input.current) {
input.current.removeEventListener("keyup", handleBack);
}
};
}
}, [mode, open]);
const change = (event: ChangeEvent) => {
const value = event.target.value;
if (mode === "default" || mode === "help") {
const newMode = getMode(value);
if (newMode) {
setMode(newMode);
}
}
if (mode === "orders" && isQueryValidOrderNumber(value)) {
getOrderData(getGqlOrderId(value));
}
if (mode === "catalog") {
searchCatalog(value);
}
if (mode === "customers") {
searchCustomers(value);
}
setQuery(value);
};
return [
query,
mode,
change,
getModeActions(
mode,
query,
intl,
{
catalog,
customers: maybe(
() => customers.data.search.edges.map(edge => edge.node),
[]
),
order: maybe(() => orderData.order)
},
{
createOrder,
navigate,
setMode
}
)
];
}
export default useQuickSearch;

View file

@ -1,6 +1,7 @@
import Button from "@material-ui/core/Button";
import IconButton from "@material-ui/core/IconButton";
import Snackbar from "@material-ui/core/Snackbar";
import Typography from "@material-ui/core/Typography";
import CloseIcon from "@material-ui/icons/Close";
import React from "react";
@ -15,7 +16,7 @@ interface MessageManagerState {
}
export class MessageManager extends React.Component<{}, MessageManagerState> {
state = {
state: MessageManagerState = {
message: { text: "", key: "0", onUndo: undefined },
opened: false
};
@ -55,7 +56,7 @@ export class MessageManager extends React.Component<{}, MessageManagerState> {
};
render() {
const { text, key, onUndo } = this.state.message;
const { autohide = 3000, title, text, key, onUndo } = this.state.message;
return (
<>
<Snackbar
@ -65,7 +66,7 @@ export class MessageManager extends React.Component<{}, MessageManagerState> {
vertical: "top"
}}
open={this.state.opened}
autoHideDuration={3000}
autoHideDuration={autohide}
onClose={this.handleClose}
onExited={this.handleExited}
ContentProps={{
@ -73,9 +74,15 @@ export class MessageManager extends React.Component<{}, MessageManagerState> {
}}
message={
<span id="message-id" data-tc="notification">
{title && (
<Typography variant="h5" style={{ marginBottom: "1rem" }}>
{title}
</Typography>
)}
{text}
</span>
}
title={title}
action={[
!!onUndo ? (
<Button

View file

@ -1,6 +1,8 @@
import { createContext } from "react";
export interface IMessage {
autohide?: number;
title?: string;
text: string;
onUndo?: () => void;
}

View file

@ -1,3 +1,4 @@
import packageInfo from "../package.json";
import { SearchVariables } from "./hooks/makeSearch";
import { ListSettings, ListViews } from "./types";
@ -74,3 +75,5 @@ export const defaultListSettings: AppListViewSettings = {
rowNumber: PAGINATE_BY
}
};
export const APP_VERSION = packageInfo.version;

View file

@ -150,7 +150,7 @@ const CustomerCreatePage: React.FC<CustomerCreatePageProps> = ({
</AppHeader>
<PageHeader
title={intl.formatMessage({
defaultMessage: "Add Customer",
defaultMessage: "Create Customer",
description: "page header"
})}
/>

View file

@ -50,7 +50,7 @@ const CustomerListPage: React.FC<CustomerListPageProps> = ({
onClick={onAdd}
>
<FormattedMessage
defaultMessage="Add customer"
defaultMessage="Create customer"
description="button"
/>
</Button>

View file

@ -11,15 +11,15 @@ import { commonMessages } from "@saleor/intl";
import { maybe } from "@saleor/misc";
import useNotifier from "./useNotifier";
type UseMutation<TData, TVariables> = [
export type UseMutation<TData, TVariables> = [
MutationFunction<TData, TVariables>,
MutationResult<TData>
];
type UseMutationCbs<TData> = Partial<{
export type UseMutationCbs<TData> = Partial<{
onCompleted: (data: TData) => void;
onError: (error: ApolloError) => void;
}>;
type UseMutationHook<TData, TVariables> = (
export type UseMutationHook<TData, TVariables> = (
cbs: UseMutationCbs<TData>
) => UseMutation<TData, TVariables>;

View file

@ -6,12 +6,13 @@ function useDebounce<T>(
time = 200
): UseDebounceFn<T> {
const timer = useRef(null);
useEffect(() => () => clearTimeout(timer.current));
useEffect(() => () => clearTimeout(timer.current), []);
return (...args: T[]) => {
if (timer.current) {
clearTimeout(timer.current);
}
timer.current = setTimeout(() => debounceFn(...args), time);
};
}

View file

@ -12,6 +12,7 @@ import ErrorBoundary from "react-error-boundary";
import { useIntl } from "react-intl";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import Navigator from "@saleor/components/Navigator";
import useAppState from "@saleor/hooks/useAppState";
import AttributeSection from "./attributes";
import { attributeSection } from "./attributes/urls";
@ -153,6 +154,7 @@ const Routes: React.FC = () => {
}) =>
isAuthenticated && !tokenAuthLoading && !tokenVerifyLoading ? (
<AppLayout>
<Navigator />
<ErrorBoundary
onError={() =>
dispatchAppState({

View file

@ -26,8 +26,7 @@ import OrderUnfulfilledItems from "../OrderUnfulfilledItems/OrderUnfulfilledItem
const useStyles = makeStyles(
theme => ({
date: {
marginBottom: theme.spacing(3),
marginTop: -theme.spacing(2)
marginBottom: theme.spacing(3)
},
header: {
display: "flex",

View file

@ -15,22 +15,30 @@ export const clients: SearchCustomers_search_edges_node[] = [
{
__typename: "User" as "User",
email: "test.client1@example.com",
id: "c1"
firstName: "John",
id: "c1",
lastName: "Doe"
},
{
__typename: "User" as "User",
email: "test.client2@example.com",
id: "c2"
firstName: "Dough",
id: "c2",
lastName: "Jones"
},
{
__typename: "User" as "User",
email: "test.client3@example.com",
id: "c3"
firstName: "Jonas",
id: "c3",
lastName: "Dough"
},
{
__typename: "User" as "User",
email: "test.client4@example.com",
id: "c4"
firstName: "Bill",
id: "c4",
lastName: "Jonas"
}
];
export const orders: OrderList_orders_edges_node[] = [

View file

@ -1,5 +1,6 @@
import gql from "graphql-tag";
import makeMutation from "@saleor/hooks/makeMutation";
import { TypedMutation } from "../mutations";
import {
fragmentAddress,
@ -409,10 +410,9 @@ const orderDraftCreateMutation = gql`
}
}
`;
export const TypedOrderDraftCreateMutation = TypedMutation<
OrderDraftCreate,
{}
>(orderDraftCreateMutation);
export const useOrderDraftCreateMutation = makeMutation<OrderDraftCreate, {}>(
orderDraftCreateMutation
);
const orderLineDeleteMutation = gql`
${fragmentOrderDetails}

View file

@ -21,7 +21,7 @@ import { ListViews } from "@saleor/types";
import OrderDraftListPage from "../../components/OrderDraftListPage";
import {
TypedOrderDraftBulkCancelMutation,
TypedOrderDraftCreateMutation
useOrderDraftCreateMutation
} from "../../mutations";
import { TypedOrderDraftListQuery } from "../../queries";
import { OrderDraftBulkCancel } from "../../types/OrderDraftBulkCancel";
@ -58,6 +58,19 @@ export const OrderDraftList: React.FC<OrderDraftListProps> = ({ params }) => {
);
const intl = useIntl();
const handleCreateOrderCreateSuccess = (data: OrderDraftCreate) => {
notify({
text: intl.formatMessage({
defaultMessage: "Order draft succesfully created"
})
});
navigate(orderUrl(data.draftOrderCreate.order.id));
};
const [createOrder] = useOrderDraftCreateMutation({
onCompleted: handleCreateOrderCreateSuccess
});
const tabs = getFilterTabs();
const currentTab =
@ -88,15 +101,6 @@ export const OrderDraftList: React.FC<OrderDraftListProps> = ({ params }) => {
true
);
const handleCreateOrderCreateSuccess = (data: OrderDraftCreate) => {
notify({
text: intl.formatMessage({
defaultMessage: "Order draft succesfully created"
})
});
navigate(orderUrl(data.draftOrderCreate.order.id));
};
const openModal = (action: OrderDraftListUrlDialog, ids?: string[]) =>
navigate(
orderDraftListUrl({
@ -137,140 +141,133 @@ export const OrderDraftList: React.FC<OrderDraftListProps> = ({ params }) => {
);
return (
<TypedOrderDraftCreateMutation onCompleted={handleCreateOrderCreateSuccess}>
{createOrder => (
<TypedOrderDraftListQuery displayLoader variables={queryVariables}>
{({ data, loading, refetch }) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.draftOrders.pageInfo),
paginationState,
params
);
<TypedOrderDraftListQuery displayLoader variables={queryVariables}>
{({ data, loading, refetch }) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.draftOrders.pageInfo),
paginationState,
params
);
const handleOrderDraftBulkCancel = (data: OrderDraftBulkCancel) => {
if (data.draftOrderBulkDelete.errors.length === 0) {
notify({
text: intl.formatMessage({
defaultMessage: "Deleted draft orders"
})
const handleOrderDraftBulkCancel = (data: OrderDraftBulkCancel) => {
if (data.draftOrderBulkDelete.errors.length === 0) {
notify({
text: intl.formatMessage({
defaultMessage: "Deleted draft orders"
})
});
refetch();
reset();
closeModal();
}
};
return (
<TypedOrderDraftBulkCancelMutation
onCompleted={handleOrderDraftBulkCancel}
>
{(orderDraftBulkDelete, orderDraftBulkDeleteOpts) => {
const bulkRemoveTransitionState = getMutationState(
orderDraftBulkDeleteOpts.called,
orderDraftBulkDeleteOpts.loading,
maybe(
() =>
orderDraftBulkDeleteOpts.data.draftOrderBulkDelete.errors
)
);
const onOrderDraftBulkDelete = () =>
orderDraftBulkDelete({
variables: {
ids: params.ids
}
});
refetch();
reset();
closeModal();
}
};
return (
<TypedOrderDraftBulkCancelMutation
onCompleted={handleOrderDraftBulkCancel}
>
{(orderDraftBulkDelete, orderDraftBulkDeleteOpts) => {
const bulkRemoveTransitionState = getMutationState(
orderDraftBulkDeleteOpts.called,
orderDraftBulkDeleteOpts.loading,
maybe(
() =>
orderDraftBulkDeleteOpts.data.draftOrderBulkDelete
.errors
)
);
const onOrderDraftBulkDelete = () =>
orderDraftBulkDelete({
variables: {
ids: params.ids
}
});
return (
<>
<OrderDraftListPage
currentTab={currentTab}
initialSearch={params.query || ""}
onSearchChange={query => changeFilterField({ query })}
onAll={() => navigate(orderDraftListUrl())}
onTabChange={handleTabChange}
onTabDelete={() => openModal("delete-search")}
onTabSave={() => openModal("save-search")}
tabs={tabs.map(tab => tab.name)}
disabled={loading}
settings={settings}
orders={maybe(() =>
data.draftOrders.edges.map(edge => edge.node)
)}
pageInfo={pageInfo}
onAdd={createOrder}
onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage}
onUpdateListSettings={updateListSettings}
onRowClick={id => () => navigate(orderUrl(id))}
isChecked={isSelected}
selected={listElements.length}
toggle={toggle}
toggleAll={toggleAll}
toolbar={
<IconButton
color="primary"
onClick={() =>
navigate(
orderDraftListUrl({
action: "remove",
ids: listElements
})
)
}
>
<DeleteIcon />
</IconButton>
return (
<>
<OrderDraftListPage
currentTab={currentTab}
initialSearch={params.query || ""}
onSearchChange={query => changeFilterField({ query })}
onAll={() => navigate(orderDraftListUrl())}
onTabChange={handleTabChange}
onTabDelete={() => openModal("delete-search")}
onTabSave={() => openModal("save-search")}
tabs={tabs.map(tab => tab.name)}
disabled={loading}
settings={settings}
orders={maybe(() =>
data.draftOrders.edges.map(edge => edge.node)
)}
pageInfo={pageInfo}
onAdd={createOrder}
onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage}
onUpdateListSettings={updateListSettings}
onRowClick={id => () => navigate(orderUrl(id))}
isChecked={isSelected}
selected={listElements.length}
toggle={toggle}
toggleAll={toggleAll}
toolbar={
<IconButton
color="primary"
onClick={() =>
navigate(
orderDraftListUrl({
action: "remove",
ids: listElements
})
)
}
/>
<ActionDialog
confirmButtonState={bulkRemoveTransitionState}
onClose={closeModal}
onConfirm={onOrderDraftBulkDelete}
open={params.action === "remove"}
title={intl.formatMessage({
defaultMessage: "Delete Order Drafts",
description: "dialog header"
})}
variant="delete"
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to delete {counter,plural,one{this order draft} other{{displayQuantity} orderDrafts}}?"
description="dialog content"
values={{
counter: maybe(() => params.ids.length),
displayQuantity: (
<strong>
{maybe(() => params.ids.length)}
</strong>
)
}}
/>
</DialogContentText>
</ActionDialog>
<SaveFilterTabDialog
open={params.action === "save-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabSave}
<DeleteIcon />
</IconButton>
}
/>
<ActionDialog
confirmButtonState={bulkRemoveTransitionState}
onClose={closeModal}
onConfirm={onOrderDraftBulkDelete}
open={params.action === "remove"}
title={intl.formatMessage({
defaultMessage: "Delete Order Drafts",
description: "dialog header"
})}
variant="delete"
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to delete {counter,plural,one{this order draft} other{{displayQuantity} orderDrafts}}?"
description="dialog content"
values={{
counter: maybe(() => params.ids.length),
displayQuantity: (
<strong>{maybe(() => params.ids.length)}</strong>
)
}}
/>
<DeleteFilterTabDialog
open={params.action === "delete-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabDelete}
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
/>
</>
);
}}
</TypedOrderDraftBulkCancelMutation>
);
}}
</TypedOrderDraftListQuery>
)}
</TypedOrderDraftCreateMutation>
</DialogContentText>
</ActionDialog>
<SaveFilterTabDialog
open={params.action === "save-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabSave}
/>
<DeleteFilterTabDialog
open={params.action === "delete-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabDelete}
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
/>
</>
);
}}
</TypedOrderDraftBulkCancelMutation>
);
}}
</TypedOrderDraftListQuery>
);
};

View file

@ -21,7 +21,7 @@ import OrderBulkCancelDialog from "../../components/OrderBulkCancelDialog";
import OrderListPage from "../../components/OrderListPage/OrderListPage";
import {
TypedOrderBulkCancelMutation,
TypedOrderDraftCreateMutation
useOrderDraftCreateMutation
} from "../../mutations";
import { TypedOrderListQuery } from "../../queries";
import { OrderBulkCancel } from "../../types/OrderBulkCancel";
@ -62,6 +62,19 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
);
const intl = useIntl();
const handleCreateOrderCreateSuccess = (data: OrderDraftCreate) => {
notify({
text: intl.formatMessage({
defaultMessage: "Order draft succesfully created"
})
});
navigate(orderUrl(data.draftOrderCreate.order.id));
};
const [createOrder] = useOrderDraftCreateMutation({
onCompleted: handleCreateOrderCreateSuccess
});
const tabs = getFilterTabs();
const currentTab =
@ -130,15 +143,6 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
const paginationState = createPaginationState(settings.rowNumber, params);
const currencySymbol = maybe(() => shop.defaultCurrency, "USD");
const handleCreateOrderCreateSuccess = (data: OrderDraftCreate) => {
notify({
text: intl.formatMessage({
defaultMessage: "Order draft succesfully created"
})
});
navigate(orderUrl(data.draftOrderCreate.order.id));
};
const queryVariables = React.useMemo(
() => ({
...paginationState,
@ -148,131 +152,127 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
);
return (
<TypedOrderDraftCreateMutation onCompleted={handleCreateOrderCreateSuccess}>
{createOrder => (
<TypedOrderListQuery displayLoader variables={queryVariables}>
{({ data, loading, refetch }) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.orders.pageInfo),
paginationState,
params
);
<TypedOrderListQuery displayLoader variables={queryVariables}>
{({ data, loading, refetch }) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.orders.pageInfo),
paginationState,
params
);
const handleOrderBulkCancel = (data: OrderBulkCancel) => {
if (data.orderBulkCancel.errors.length === 0) {
notify({
text: intl.formatMessage({
defaultMessage: "Orders cancelled"
})
const handleOrderBulkCancel = (data: OrderBulkCancel) => {
if (data.orderBulkCancel.errors.length === 0) {
notify({
text: intl.formatMessage({
defaultMessage: "Orders cancelled"
})
});
reset();
refetch();
closeModal();
}
};
return (
<TypedOrderBulkCancelMutation onCompleted={handleOrderBulkCancel}>
{(orderBulkCancel, orderBulkCancelOpts) => {
const orderBulkCancelTransitionState = getMutationState(
orderBulkCancelOpts.called,
orderBulkCancelOpts.loading,
maybe(() => orderBulkCancelOpts.data.orderBulkCancel.errors)
);
const onOrderBulkCancel = (restock: boolean) =>
orderBulkCancel({
variables: {
ids: params.ids,
restock
}
});
reset();
refetch();
closeModal();
}
};
return (
<TypedOrderBulkCancelMutation onCompleted={handleOrderBulkCancel}>
{(orderBulkCancel, orderBulkCancelOpts) => {
const orderBulkCancelTransitionState = getMutationState(
orderBulkCancelOpts.called,
orderBulkCancelOpts.loading,
maybe(() => orderBulkCancelOpts.data.orderBulkCancel.errors)
);
const onOrderBulkCancel = (restock: boolean) =>
orderBulkCancel({
variables: {
ids: params.ids,
restock
}
});
return (
<>
<OrderListPage
currencySymbol={currencySymbol}
settings={settings}
filtersList={createFilterChips(
params,
{
formatDate
},
changeFilterField,
intl
)}
currentTab={currentTab}
disabled={loading}
orders={maybe(() =>
data.orders.edges.map(edge => edge.node)
)}
pageInfo={pageInfo}
onAdd={createOrder}
onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage}
onUpdateListSettings={updateListSettings}
onRowClick={id => () => navigate(orderUrl(id))}
isChecked={isSelected}
selected={listElements.length}
toggle={toggle}
toggleAll={toggleAll}
toolbar={
<Button
color="primary"
onClick={() => openModal("cancel", listElements)}
>
<FormattedMessage
defaultMessage="Cancel"
description="cancel orders, button"
/>
</Button>
}
onSearchChange={query => changeFilterField({ query })}
onFilterAdd={data =>
changeFilterField(createFilter(params, data))
}
onTabSave={() => openModal("save-search")}
onTabDelete={() => openModal("delete-search")}
onTabChange={handleTabChange}
initialSearch={params.query || ""}
tabs={getFilterTabs().map(tab => tab.name)}
onAll={() =>
changeFilters({
status: undefined
})
}
/>
<OrderBulkCancelDialog
confirmButtonState={orderBulkCancelTransitionState}
numberOfOrders={maybe(
() => params.ids.length.toString(),
"..."
)}
onClose={closeModal}
onConfirm={onOrderBulkCancel}
open={params.action === "cancel"}
/>
<SaveFilterTabDialog
open={params.action === "save-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleFilterTabSave}
/>
<DeleteFilterTabDialog
open={params.action === "delete-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleFilterTabDelete}
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
/>
</>
);
}}
</TypedOrderBulkCancelMutation>
);
}}
</TypedOrderListQuery>
)}
</TypedOrderDraftCreateMutation>
return (
<>
<OrderListPage
currencySymbol={currencySymbol}
settings={settings}
filtersList={createFilterChips(
params,
{
formatDate
},
changeFilterField,
intl
)}
currentTab={currentTab}
disabled={loading}
orders={maybe(() =>
data.orders.edges.map(edge => edge.node)
)}
pageInfo={pageInfo}
onAdd={createOrder}
onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage}
onUpdateListSettings={updateListSettings}
onRowClick={id => () => navigate(orderUrl(id))}
isChecked={isSelected}
selected={listElements.length}
toggle={toggle}
toggleAll={toggleAll}
toolbar={
<Button
color="primary"
onClick={() => openModal("cancel", listElements)}
>
<FormattedMessage
defaultMessage="Cancel"
description="cancel orders, button"
/>
</Button>
}
onSearchChange={query => changeFilterField({ query })}
onFilterAdd={data =>
changeFilterField(createFilter(params, data))
}
onTabSave={() => openModal("save-search")}
onTabDelete={() => openModal("delete-search")}
onTabChange={handleTabChange}
initialSearch={params.query || ""}
tabs={getFilterTabs().map(tab => tab.name)}
onAll={() =>
changeFilters({
status: undefined
})
}
/>
<OrderBulkCancelDialog
confirmButtonState={orderBulkCancelTransitionState}
numberOfOrders={maybe(
() => params.ids.length.toString(),
"..."
)}
onClose={closeModal}
onConfirm={onOrderBulkCancel}
open={params.action === "cancel"}
/>
<SaveFilterTabDialog
open={params.action === "save-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleFilterTabSave}
/>
<DeleteFilterTabDialog
open={params.action === "delete-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleFilterTabDelete}
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
/>
</>
);
}}
</TypedOrderBulkCancelMutation>
);
}}
</TypedOrderListQuery>
);
};

View file

@ -2,10 +2,7 @@
/* eslint-disable */
// This file was automatically generated and should not be edited.
import {
PluginUpdateInput,
ConfigurationTypeFieldEnum
} from "./../../types/globalTypes";
import { PluginUpdateInput, ConfigurationTypeFieldEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: PluginUpdate
@ -32,9 +29,7 @@ export interface PluginUpdate_pluginUpdate_plugin {
name: string;
description: string;
active: boolean;
configuration:
| (PluginUpdate_pluginUpdate_plugin_configuration | null)[]
| null;
configuration: (PluginUpdate_pluginUpdate_plugin_configuration | null)[] | null;
}
export interface PluginUpdate_pluginUpdate {

View file

@ -10,6 +10,8 @@ export interface SearchCustomers_search_edges_node {
__typename: "User";
id: string;
email: string;
firstName: string;
lastName: string;
}
export interface SearchCustomers_search_edges {

View file

@ -22,9 +22,7 @@ export interface SearchProductTypes_search_edges_node_productAttributes {
slug: string | null;
name: string | null;
valueRequired: boolean;
values:
| (SearchProductTypes_search_edges_node_productAttributes_values | null)[]
| null;
values: (SearchProductTypes_search_edges_node_productAttributes_values | null)[] | null;
}
export interface SearchProductTypes_search_edges_node {
@ -32,9 +30,7 @@ export interface SearchProductTypes_search_edges_node {
id: string;
name: string;
hasVariants: boolean;
productAttributes:
| (SearchProductTypes_search_edges_node_productAttributes | null)[]
| null;
productAttributes: (SearchProductTypes_search_edges_node_productAttributes | null)[] | null;
}
export interface SearchProductTypes_search_edges {

View file

@ -15,6 +15,8 @@ export const searchCustomers = gql`
node {
id
email
firstName
lastName
}
}
pageInfo {

View file

@ -2,12 +2,7 @@
/* eslint-disable */
// This file was automatically generated and should not be edited.
import {
SiteDomainInput,
ShopSettingsInput,
AddressInput,
AuthorizationKeyType
} from "./../../types/globalTypes";
import { SiteDomainInput, ShopSettingsInput, AddressInput, AuthorizationKeyType } from "./../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: ShopSettingsUpdate

View file

@ -29700,7 +29700,7 @@ exports[`Storyshots Views / Customers / Create customer default 1`] = `
<div
class="MuiTypography-root-id makeStyles-title-id MuiTypography-h5-id"
>
Add Customer
Create Customer
</div>
<div
class="ExtendedPageHeader-action-id"
@ -30406,7 +30406,7 @@ exports[`Storyshots Views / Customers / Create customer form errors 1`] = `
<div
class="MuiTypography-root-id makeStyles-title-id MuiTypography-h5-id"
>
Add Customer
Create Customer
</div>
<div
class="ExtendedPageHeader-action-id"
@ -31172,7 +31172,7 @@ exports[`Storyshots Views / Customers / Create customer loading 1`] = `
<div
class="MuiTypography-root-id makeStyles-title-id MuiTypography-h5-id"
>
Add Customer
Create Customer
</div>
<div
class="ExtendedPageHeader-action-id"
@ -36701,7 +36701,7 @@ exports[`Storyshots Views / Customers / Customer list default 1`] = `
<span
class="MuiButton-label-id"
>
Add customer
Create customer
</span>
</button>
</div>
@ -37802,7 +37802,7 @@ exports[`Storyshots Views / Customers / Customer list loading 1`] = `
<span
class="MuiButton-label-id"
>
Add customer
Create customer
</span>
</button>
</div>
@ -38131,7 +38131,7 @@ exports[`Storyshots Views / Customers / Customer list no data 1`] = `
<span
class="MuiButton-label-id"
>
Add customer
Create customer
</span>
</button>
</div>

View file

@ -340,7 +340,8 @@ export default (colors: IThemeColors): Theme =>
"& svg": {
color: colors.font.default
}
}
},
alignSelf: "baseline"
},
message: {
fontSize: 16
@ -349,7 +350,10 @@ export default (colors: IThemeColors): Theme =>
backgroundColor: colors.background.paper,
boxShadow:
"0 6px 10px 0px rgba(0, 0, 0, 0.15), 0 1px 18px 0px rgba(0, 0, 0, 0.12), 0 3px 5px -1px rgba(0, 0, 0, 0.10)",
color: colors.font.default
color: colors.font.default,
display: "grid",
gridTemplateColumns: "1fr 56px",
maxWidth: 480
}
},
MuiSwitch: {

View file

@ -8,41 +8,41 @@
export enum AddressTypeEnum {
BILLING = "BILLING",
SHIPPING = "SHIPPING"
SHIPPING = "SHIPPING",
}
export enum AttributeInputTypeEnum {
DROPDOWN = "DROPDOWN",
MULTISELECT = "MULTISELECT"
MULTISELECT = "MULTISELECT",
}
export enum AttributeTypeEnum {
PRODUCT = "PRODUCT",
VARIANT = "VARIANT"
VARIANT = "VARIANT",
}
export enum AttributeValueType {
COLOR = "COLOR",
GRADIENT = "GRADIENT",
STRING = "STRING",
URL = "URL"
URL = "URL",
}
export enum AuthorizationKeyType {
FACEBOOK = "FACEBOOK",
GOOGLE_OAUTH2 = "GOOGLE_OAUTH2"
GOOGLE_OAUTH2 = "GOOGLE_OAUTH2",
}
export enum CollectionPublished {
HIDDEN = "HIDDEN",
PUBLISHED = "PUBLISHED"
PUBLISHED = "PUBLISHED",
}
export enum ConfigurationTypeFieldEnum {
BOOLEAN = "BOOLEAN",
PASSWORD = "PASSWORD",
SECRET = "SECRET",
STRING = "STRING"
STRING = "STRING",
}
export enum CountryCode {
@ -295,23 +295,23 @@ export enum CountryCode {
YT = "YT",
ZA = "ZA",
ZM = "ZM",
ZW = "ZW"
ZW = "ZW",
}
export enum DiscountStatusEnum {
ACTIVE = "ACTIVE",
EXPIRED = "EXPIRED",
SCHEDULED = "SCHEDULED"
SCHEDULED = "SCHEDULED",
}
export enum DiscountValueTypeEnum {
FIXED = "FIXED",
PERCENTAGE = "PERCENTAGE"
PERCENTAGE = "PERCENTAGE",
}
export enum FulfillmentStatus {
CANCELED = "CANCELED",
FULFILLED = "FULFILLED"
FULFILLED = "FULFILLED",
}
export enum LanguageCodeEnum {
@ -357,19 +357,19 @@ export enum LanguageCodeEnum {
UK = "UK",
VI = "VI",
ZH_HANS = "ZH_HANS",
ZH_HANT = "ZH_HANT"
ZH_HANT = "ZH_HANT",
}
export enum OrderAction {
CAPTURE = "CAPTURE",
MARK_AS_PAID = "MARK_AS_PAID",
REFUND = "REFUND",
VOID = "VOID"
VOID = "VOID",
}
export enum OrderDirection {
ASC = "ASC",
DESC = "DESC"
DESC = "DESC",
}
export enum OrderEventsEmailsEnum {
@ -378,7 +378,7 @@ export enum OrderEventsEmailsEnum {
ORDER_CONFIRMATION = "ORDER_CONFIRMATION",
PAYMENT_CONFIRMATION = "PAYMENT_CONFIRMATION",
SHIPPING_CONFIRMATION = "SHIPPING_CONFIRMATION",
TRACKING_UPDATED = "TRACKING_UPDATED"
TRACKING_UPDATED = "TRACKING_UPDATED",
}
export enum OrderEventsEnum {
@ -402,7 +402,7 @@ export enum OrderEventsEnum {
PLACED = "PLACED",
PLACED_FROM_DRAFT = "PLACED_FROM_DRAFT",
TRACKING_UPDATED = "TRACKING_UPDATED",
UPDATED_ADDRESS = "UPDATED_ADDRESS"
UPDATED_ADDRESS = "UPDATED_ADDRESS",
}
export enum OrderStatus {
@ -410,7 +410,7 @@ export enum OrderStatus {
DRAFT = "DRAFT",
FULFILLED = "FULFILLED",
PARTIALLY_FULFILLED = "PARTIALLY_FULFILLED",
UNFULFILLED = "UNFULFILLED"
UNFULFILLED = "UNFULFILLED",
}
export enum OrderStatusFilter {
@ -419,7 +419,7 @@ export enum OrderStatusFilter {
PARTIALLY_FULFILLED = "PARTIALLY_FULFILLED",
READY_TO_CAPTURE = "READY_TO_CAPTURE",
READY_TO_FULFILL = "READY_TO_FULFILL",
UNFULFILLED = "UNFULFILLED"
UNFULFILLED = "UNFULFILLED",
}
export enum PaymentChargeStatusEnum {
@ -427,7 +427,7 @@ export enum PaymentChargeStatusEnum {
FULLY_REFUNDED = "FULLY_REFUNDED",
NOT_CHARGED = "NOT_CHARGED",
PARTIALLY_CHARGED = "PARTIALLY_CHARGED",
PARTIALLY_REFUNDED = "PARTIALLY_REFUNDED"
PARTIALLY_REFUNDED = "PARTIALLY_REFUNDED",
}
export enum PermissionEnum {
@ -445,7 +445,7 @@ export enum PermissionEnum {
MANAGE_STAFF = "MANAGE_STAFF",
MANAGE_TRANSLATIONS = "MANAGE_TRANSLATIONS",
MANAGE_USERS = "MANAGE_USERS",
MANAGE_WEBHOOKS = "MANAGE_WEBHOOKS"
MANAGE_WEBHOOKS = "MANAGE_WEBHOOKS",
}
export enum ProductErrorCode {
@ -459,7 +459,7 @@ export enum ProductErrorCode {
NOT_PRODUCTS_IMAGE = "NOT_PRODUCTS_IMAGE",
REQUIRED = "REQUIRED",
UNIQUE = "UNIQUE",
VARIANT_NO_DIGITAL_CONTENT = "VARIANT_NO_DIGITAL_CONTENT"
VARIANT_NO_DIGITAL_CONTENT = "VARIANT_NO_DIGITAL_CONTENT",
}
export enum ProductOrderField {
@ -468,37 +468,37 @@ export enum ProductOrderField {
NAME = "NAME",
PRICE = "PRICE",
PUBLISHED = "PUBLISHED",
TYPE = "TYPE"
TYPE = "TYPE",
}
export enum ProductTypeConfigurable {
CONFIGURABLE = "CONFIGURABLE",
SIMPLE = "SIMPLE"
SIMPLE = "SIMPLE",
}
export enum ProductTypeEnum {
DIGITAL = "DIGITAL",
SHIPPABLE = "SHIPPABLE"
SHIPPABLE = "SHIPPABLE",
}
export enum SaleType {
FIXED = "FIXED",
PERCENTAGE = "PERCENTAGE"
PERCENTAGE = "PERCENTAGE",
}
export enum ShippingMethodTypeEnum {
PRICE = "PRICE",
WEIGHT = "WEIGHT"
WEIGHT = "WEIGHT",
}
export enum StaffMemberStatus {
ACTIVE = "ACTIVE",
DEACTIVATED = "DEACTIVATED"
DEACTIVATED = "DEACTIVATED",
}
export enum StockAvailability {
IN_STOCK = "IN_STOCK",
OUT_OF_STOCK = "OUT_OF_STOCK"
OUT_OF_STOCK = "OUT_OF_STOCK",
}
export enum TaxRateType {
@ -526,19 +526,19 @@ export enum TaxRateType {
SOCIAL_HOUSING = "SOCIAL_HOUSING",
STANDARD = "STANDARD",
WATER = "WATER",
WINE = "WINE"
WINE = "WINE",
}
export enum VoucherDiscountType {
FIXED = "FIXED",
PERCENTAGE = "PERCENTAGE",
SHIPPING = "SHIPPING"
SHIPPING = "SHIPPING",
}
export enum VoucherTypeEnum {
ENTIRE_ORDER = "ENTIRE_ORDER",
SHIPPING = "SHIPPING",
SPECIFIC_PRODUCT = "SPECIFIC_PRODUCT"
SPECIFIC_PRODUCT = "SPECIFIC_PRODUCT",
}
export enum WebhookErrorCode {
@ -546,7 +546,7 @@ export enum WebhookErrorCode {
INVALID = "INVALID",
NOT_FOUND = "NOT_FOUND",
REQUIRED = "REQUIRED",
UNIQUE = "UNIQUE"
UNIQUE = "UNIQUE",
}
export enum WebhookEventTypeEnum {
@ -557,14 +557,14 @@ export enum WebhookEventTypeEnum {
ORDER_FULFILLED = "ORDER_FULFILLED",
ORDER_FULLY_PAID = "ORDER_FULLY_PAID",
ORDER_UPDATED = "ORDER_UPDATED",
PRODUCT_CREATED = "PRODUCT_CREATED"
PRODUCT_CREATED = "PRODUCT_CREATED",
}
export enum WeightUnitsEnum {
G = "G",
KG = "KG",
LB = "LB",
OZ = "OZ"
OZ = "OZ",
}
export interface AddressInput {