From 8b36ca5308f2f5d93e1abea800e6a7309eff95e2 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Wed, 20 Nov 2019 16:58:17 +0100 Subject: [PATCH 01/22] Add basic navigator --- package-lock.json | 5 + package.json | 1 + src/components/Navigator/Navigator.tsx | 78 +++++++++++ src/components/Navigator/NavigatorInput.tsx | 42 ++++++ src/components/Navigator/NavigatorSection.tsx | 82 +++++++++++ src/components/Navigator/index.ts | 2 + src/components/Navigator/types.ts | 8 ++ src/components/Navigator/useQuickSearch.ts | 39 ++++++ src/components/Navigator/views.ts | 132 ++++++++++++++++++ src/index.tsx | 2 + 10 files changed, 391 insertions(+) create mode 100644 src/components/Navigator/Navigator.tsx create mode 100644 src/components/Navigator/NavigatorInput.tsx create mode 100644 src/components/Navigator/NavigatorSection.tsx create mode 100644 src/components/Navigator/index.ts create mode 100644 src/components/Navigator/types.ts create mode 100644 src/components/Navigator/useQuickSearch.ts create mode 100644 src/components/Navigator/views.ts diff --git a/package-lock.json b/package-lock.json index 37d527f1a..3af4cf4fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11333,6 +11333,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", diff --git a/package.json b/package.json index 31d82bf87..52c21d332 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/Navigator/Navigator.tsx b/src/components/Navigator/Navigator.tsx new file mode 100644 index 000000000..1f9adbe28 --- /dev/null +++ b/src/components/Navigator/Navigator.tsx @@ -0,0 +1,78 @@ +import Dialog from "@material-ui/core/Dialog"; +import Downshift from "downshift"; +import hotkeys from "hotkeys-js"; +import React from "react"; +import { useIntl } from "react-intl"; + +import useNavigator from "@saleor/hooks/useNavigator"; +import NavigatorInput from "./NavigatorInput"; +import NavigatorSection from "./NavigatorSection"; +import { QuickSearchAction } from "./types"; +import useQuickSearch from "./useQuickSearch"; +import { getViews, hasViews } from "./views"; + +const navigatorHotkey = "ctrl+m, command+m"; + +const Navigator: React.FC = () => { + const [visible, setVisible] = React.useState(false); + const [query, change, actions] = useQuickSearch(visible); + const navigate = useNavigator(); + const intl = useIntl(); + + React.useEffect(() => { + hotkeys(navigatorHotkey, () => setVisible(true)); + + return () => hotkeys.unbind(navigatorHotkey); + }, []); + + return ( + setVisible(false)} + fullWidth + maxWidth="sm" + > + (item ? item.label : "")} + onSelect={(item: QuickSearchAction) => { + navigate(item.url); + setVisible(false); + }} + onInputValueChange={value => + change({ + target: { + name: "query", + value + } + }) + } + defaultHighlightedIndex={0} + > + {({ getInputProps, getItemProps, highlightedIndex }) => ( +
+ + {hasViews(actions) && ( + + )} +
+ )} +
+
+ ); +}; + +export default Navigator; diff --git a/src/components/Navigator/NavigatorInput.tsx b/src/components/Navigator/NavigatorInput.tsx new file mode 100644 index 000000000..af78b3a93 --- /dev/null +++ b/src/components/Navigator/NavigatorInput.tsx @@ -0,0 +1,42 @@ +import makeStyles from "@material-ui/core/styles/makeStyles"; +import React from "react"; +import { useIntl } from "react-intl"; + +const useStyles = makeStyles( + theme => ({ + root: { + background: theme.palette.background.default, + border: "none", + color: theme.palette.text.primary, + fontSize: 24, + outline: 0, + padding: theme.spacing(2, 3), + width: "100%" + } + }), + { + name: "NavigatorInput" + } +); +const NavigatorInput: React.FC< + React.InputHTMLAttributes +> = props => { + const classes = useStyles(props); + const intl = useIntl(); + + return ( + + ); +}; + +NavigatorInput.displayName = "NavigatorInput"; +export default NavigatorInput; diff --git a/src/components/Navigator/NavigatorSection.tsx b/src/components/Navigator/NavigatorSection.tsx new file mode 100644 index 000000000..c4c5b0623 --- /dev/null +++ b/src/components/Navigator/NavigatorSection.tsx @@ -0,0 +1,82 @@ +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 + }, + margin: theme.spacing(1, 0) + }, + label: { + paddingLeft: theme.spacing(2), + textTransform: "uppercase" + }, + root: { + "&:not(:last-child)": { + marginBottom: theme.spacing(3) + }, + margin: theme.spacing(2, 0), + padding: theme.spacing(0, 1) + } + }), + { + name: "NavigatorSection" + } +); + +const NavigatorSection: React.FC = props => { + const { getItemProps, highlightedIndex, label, items, offset } = props; + + const classes = useStyles(props); + + return ( +
+ + {label} + +
+ {items.map((item, itemIndex) => { + const index = offset + itemIndex; + const itemProps = getItemProps({ + index, + item + }); + + return ( + + {item.label} + + ); + })} +
+ ); +}; + +NavigatorSection.displayName = "NavigatorSection"; +export default NavigatorSection; diff --git a/src/components/Navigator/index.ts b/src/components/Navigator/index.ts new file mode 100644 index 000000000..8816cc013 --- /dev/null +++ b/src/components/Navigator/index.ts @@ -0,0 +1,2 @@ +export { default } from "./Navigator"; +export * from "./Navigator"; diff --git a/src/components/Navigator/types.ts b/src/components/Navigator/types.ts new file mode 100644 index 000000000..4c15f60a9 --- /dev/null +++ b/src/components/Navigator/types.ts @@ -0,0 +1,8 @@ +export type QuickSearchActionType = "view"; + +export interface QuickSearchAction { + label: string; + score: number; + type: QuickSearchActionType; + url: string; +} diff --git a/src/components/Navigator/useQuickSearch.ts b/src/components/Navigator/useQuickSearch.ts new file mode 100644 index 000000000..dc51a9967 --- /dev/null +++ b/src/components/Navigator/useQuickSearch.ts @@ -0,0 +1,39 @@ +import { useState } from "react"; +import { useIntl } from "react-intl"; + +import { ChangeEvent, FormChange } from "@saleor/hooks/useForm"; +import useModalDialogOpen from "@saleor/hooks/useModalDialogOpen"; +import { QuickSearchAction } from "./types"; +import searchInViews from "./views"; + +export type QuickSearchMode = "default" | "orders" | "customers"; +const threshold = 0.05; +const maxActions = 10; + +type UseQuickSearch = [string, FormChange, QuickSearchAction[]]; +function useQuickSearch(open: boolean): UseQuickSearch { + const [query, setQuery] = useState(""); + const [mode, setMode] = useState("default"); + const intl = useIntl(); + + useModalDialogOpen(open, { + onClose: () => setQuery("") + }); + + const change = (event: ChangeEvent) => { + const value = event.target.value; + + setQuery(value); + }; + + return [ + query, + change, + [...searchInViews(query, intl)] + .filter(action => action.score >= threshold) + .sort((a, b) => (a.score <= b.score ? 1 : -1)) + .slice(0, maxActions) + ]; +} + +export default useQuickSearch; diff --git a/src/components/Navigator/views.ts b/src/components/Navigator/views.ts new file mode 100644 index 000000000..000adeaa2 --- /dev/null +++ b/src/components/Navigator/views.ts @@ -0,0 +1,132 @@ +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 { 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 { QuickSearchAction } from "./types"; + +interface View { + label: string; + url: string; +} +function searchInViews(search: string, intl: IntlShape): QuickSearchAction[] { + 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, + score: score(view.label, search), + type: "view", + url: view.url + })); +} + +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 default searchInViews; diff --git a/src/index.tsx b/src/index.tsx index b229ec5d1..915593649 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -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 ? ( + dispatchAppState({ From 09dce08f3d50d056f266dcdbd73c27c08105becd Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 21 Nov 2019 13:13:41 +0100 Subject: [PATCH 02/22] Add order search mode --- src/components/Navigator/Navigator.tsx | 22 ++++- src/components/Navigator/NavigatorInput.tsx | 90 +++++++++++++------ .../Navigator/modes/default/actions.ts | 8 ++ .../Navigator/modes/default/default.ts | 19 ++++ .../Navigator/modes/default/index.ts | 4 + .../Navigator/{ => modes/default}/views.ts | 2 +- src/components/Navigator/modes/index.ts | 20 +++++ src/components/Navigator/modes/messages.ts | 10 +++ src/components/Navigator/modes/orders.ts | 27 ++++++ src/components/Navigator/types.ts | 4 +- src/components/Navigator/useQuickSearch.ts | 69 ++++++++++---- 11 files changed, 224 insertions(+), 51 deletions(-) create mode 100644 src/components/Navigator/modes/default/actions.ts create mode 100644 src/components/Navigator/modes/default/default.ts create mode 100644 src/components/Navigator/modes/default/index.ts rename src/components/Navigator/{ => modes/default}/views.ts (98%) create mode 100644 src/components/Navigator/modes/index.ts create mode 100644 src/components/Navigator/modes/messages.ts create mode 100644 src/components/Navigator/modes/orders.ts diff --git a/src/components/Navigator/Navigator.tsx b/src/components/Navigator/Navigator.tsx index 1f9adbe28..d58a0cd1b 100644 --- a/src/components/Navigator/Navigator.tsx +++ b/src/components/Navigator/Navigator.tsx @@ -5,17 +5,19 @@ import React from "react"; import { useIntl } from "react-intl"; import useNavigator from "@saleor/hooks/useNavigator"; +import { getActions, hasActions } from "./modes/default"; +import { getViews, hasViews } from "./modes/default/views"; import NavigatorInput from "./NavigatorInput"; import NavigatorSection from "./NavigatorSection"; import { QuickSearchAction } from "./types"; import useQuickSearch from "./useQuickSearch"; -import { getViews, hasViews } from "./views"; const navigatorHotkey = "ctrl+m, command+m"; const Navigator: React.FC = () => { const [visible, setVisible] = React.useState(false); - const [query, change, actions] = useQuickSearch(visible); + const input = React.useRef(null); + const [query, mode, change, actions] = useQuickSearch(visible, input); const navigate = useNavigator(); const intl = useIntl(); @@ -51,11 +53,25 @@ const Navigator: React.FC = () => { {({ getInputProps, getItemProps, highlightedIndex }) => (
+ {hasActions(actions) && ( + + )} {hasViews(actions) && ( { getItemProps={getItemProps} highlightedIndex={highlightedIndex} items={getViews(actions)} - offset={0} + offset={getActions(actions).length} /> )}
diff --git a/src/components/Navigator/NavigatorInput.tsx b/src/components/Navigator/NavigatorInput.tsx index af78b3a93..fcb7907f9 100644 --- a/src/components/Navigator/NavigatorInput.tsx +++ b/src/components/Navigator/NavigatorInput.tsx @@ -1,42 +1,78 @@ 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 => ({ - root: { - background: theme.palette.background.default, - border: "none", + theme => { + const typography = { color: theme.palette.text.primary, fontSize: 24, - outline: 0, - padding: theme.spacing(2, 3), - width: "100%" - } - }), + 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" } ); -const NavigatorInput: React.FC< - React.InputHTMLAttributes -> = props => { - const classes = useStyles(props); - const intl = useIntl(); - return ( - - ); -}; +interface NavigatorInputProps + extends React.InputHTMLAttributes { + mode: QuickSearchMode; +} + +const NavigatorInput = React.forwardRef( + (props, ref) => { + const { mode, ...rest } = props; + const classes = useStyles(props); + const intl = useIntl(); + + return ( +
+ {mode === "orders" && #} + +
+ ); + } +); NavigatorInput.displayName = "NavigatorInput"; export default NavigatorInput; diff --git a/src/components/Navigator/modes/default/actions.ts b/src/components/Navigator/modes/default/actions.ts new file mode 100644 index 000000000..ee7c489bd --- /dev/null +++ b/src/components/Navigator/modes/default/actions.ts @@ -0,0 +1,8 @@ +import { QuickSearchAction } 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; +} diff --git a/src/components/Navigator/modes/default/default.ts b/src/components/Navigator/modes/default/default.ts new file mode 100644 index 000000000..41f1c3e09 --- /dev/null +++ b/src/components/Navigator/modes/default/default.ts @@ -0,0 +1,19 @@ +import { IntlShape } from "react-intl"; + +import { QuickSearchAction } from "../../types"; +import searchInViews from "./views"; + +const threshold = 0.05; +const maxActions = 10; + +function getDefaultModeActions( + query: string, + intl: IntlShape +): QuickSearchAction[] { + return [...searchInViews(query, intl)] + .filter(action => action.score >= threshold) + .sort((a, b) => (a.score <= b.score ? 1 : -1)) + .slice(0, maxActions); +} + +export default getDefaultModeActions; diff --git a/src/components/Navigator/modes/default/index.ts b/src/components/Navigator/modes/default/index.ts new file mode 100644 index 000000000..445c4ca3d --- /dev/null +++ b/src/components/Navigator/modes/default/index.ts @@ -0,0 +1,4 @@ +export * from "./actions"; +export * from "./default"; +export { default } from "./default"; +export * from "./views"; diff --git a/src/components/Navigator/views.ts b/src/components/Navigator/modes/default/views.ts similarity index 98% rename from src/components/Navigator/views.ts rename to src/components/Navigator/modes/default/views.ts index 000adeaa2..8b6819967 100644 --- a/src/components/Navigator/views.ts +++ b/src/components/Navigator/modes/default/views.ts @@ -20,7 +20,7 @@ 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 { QuickSearchAction } from "./types"; +import { QuickSearchAction } from "../../types"; interface View { label: string; diff --git a/src/components/Navigator/modes/index.ts b/src/components/Navigator/modes/index.ts new file mode 100644 index 000000000..2856aaeab --- /dev/null +++ b/src/components/Navigator/modes/index.ts @@ -0,0 +1,20 @@ +import { IntlShape } from "react-intl"; + +import { QuickSearchAction, QuickSearchMode } from "../types"; +import getDefaultModeActions from "./default"; +import getOrdersModeActions from "./orders"; + +function getModeActions( + mode: QuickSearchMode, + query: string, + intl: IntlShape +): QuickSearchAction[] { + switch (mode) { + case "orders": + return getOrdersModeActions(query, intl); + default: + return getDefaultModeActions(query, intl); + } +} + +export default getModeActions; diff --git a/src/components/Navigator/modes/messages.ts b/src/components/Navigator/modes/messages.ts new file mode 100644 index 000000000..0fce6090f --- /dev/null +++ b/src/components/Navigator/modes/messages.ts @@ -0,0 +1,10 @@ +import { defineMessages } from "react-intl"; + +const messages = defineMessages({ + goToOrder: { + defaultMessage: "Go to order #{orderNumber}", + description: "navigator action" + } +}); + +export default messages; diff --git a/src/components/Navigator/modes/orders.ts b/src/components/Navigator/modes/orders.ts new file mode 100644 index 000000000..09a6a8f79 --- /dev/null +++ b/src/components/Navigator/modes/orders.ts @@ -0,0 +1,27 @@ +import { IntlShape } from "react-intl"; + +import { orderUrl } from "@saleor/orders/urls"; +import { QuickSearchAction } from "../types"; +import messages from "./messages"; + +function getOrdersModeActions( + query: string, + intl: IntlShape +): QuickSearchAction[] { + if (query === parseInt(query, 0).toString()) { + return [ + { + label: intl.formatMessage(messages.goToOrder, { + orderNumber: query + }), + score: 1, + type: "action", + url: orderUrl(btoa(`Order:${query}`)) + } + ]; + } + + return []; +} + +export default getOrdersModeActions; diff --git a/src/components/Navigator/types.ts b/src/components/Navigator/types.ts index 4c15f60a9..3646a7ce9 100644 --- a/src/components/Navigator/types.ts +++ b/src/components/Navigator/types.ts @@ -1,4 +1,4 @@ -export type QuickSearchActionType = "view"; +export type QuickSearchActionType = "action" | "view"; export interface QuickSearchAction { label: string; @@ -6,3 +6,5 @@ export interface QuickSearchAction { type: QuickSearchActionType; url: string; } + +export type QuickSearchMode = "default" | "orders" | "customers"; diff --git a/src/components/Navigator/useQuickSearch.ts b/src/components/Navigator/useQuickSearch.ts index dc51a9967..3a9798341 100644 --- a/src/components/Navigator/useQuickSearch.ts +++ b/src/components/Navigator/useQuickSearch.ts @@ -1,39 +1,70 @@ -import { useState } from "react"; +import { RefObject, useEffect, useState } from "react"; import { useIntl } from "react-intl"; import { ChangeEvent, FormChange } from "@saleor/hooks/useForm"; import useModalDialogOpen from "@saleor/hooks/useModalDialogOpen"; -import { QuickSearchAction } from "./types"; -import searchInViews from "./views"; +import getModeActions from "./modes"; +import { QuickSearchAction, QuickSearchMode } from "./types"; -export type QuickSearchMode = "default" | "orders" | "customers"; -const threshold = 0.05; -const maxActions = 10; - -type UseQuickSearch = [string, FormChange, QuickSearchAction[]]; -function useQuickSearch(open: boolean): UseQuickSearch { +type UseQuickSearch = [ + string, + QuickSearchMode, + FormChange, + QuickSearchAction[] +]; +function useQuickSearch( + open: boolean, + input: RefObject +): UseQuickSearch { const [query, setQuery] = useState(""); const [mode, setMode] = useState("default"); const intl = useIntl(); useModalDialogOpen(open, { - onClose: () => setQuery("") + 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; - setQuery(value); + if (mode === "default") { + switch (value) { + case "# ": + setMode("orders"); + default: + setQuery(value); + } + } else { + setQuery(value); + } }; - return [ - query, - change, - [...searchInViews(query, intl)] - .filter(action => action.score >= threshold) - .sort((a, b) => (a.score <= b.score ? 1 : -1)) - .slice(0, maxActions) - ]; + return [query, mode, change, getModeActions(mode, query, intl)]; } export default useQuickSearch; From dca00c425f275429c73276498a7ffe2cc3e805bf Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 21 Nov 2019 18:02:21 +0100 Subject: [PATCH 03/22] Fix order placement date position --- src/orders/components/OrderDetailsPage/OrderDetailsPage.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/orders/components/OrderDetailsPage/OrderDetailsPage.tsx b/src/orders/components/OrderDetailsPage/OrderDetailsPage.tsx index 886d46d9c..18bc44472 100644 --- a/src/orders/components/OrderDetailsPage/OrderDetailsPage.tsx +++ b/src/orders/components/OrderDetailsPage/OrderDetailsPage.tsx @@ -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", From 2c0e3bb4bf50261ee66f762f42afb1124b210433 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 21 Nov 2019 18:46:28 +0100 Subject: [PATCH 04/22] Do not format type files --- .gitignore | 1 + .prettierignore | 1 + 2 files changed, 2 insertions(+) create mode 100644 .prettierignore diff --git a/.gitignore b/.gitignore index 14f66e4e6..f5c4f1375 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ !.nvmrc !.npmrc !.plop +!.prettierignore !.pylintrc !.travis* !.testcafe diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..bcbfbbf22 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +types/ \ No newline at end of file From 564aab26f779f7f138056429aa9d92a237d52e4b Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 21 Nov 2019 18:47:06 +0100 Subject: [PATCH 05/22] Debounce order search --- src/components/Navigator/modes/index.ts | 10 ++- src/components/Navigator/modes/orders.ts | 19 +++++- src/components/Navigator/modes/types.ts | 5 ++ .../queries/types/CheckIfOrderExists.ts | 23 +++++++ .../queries/useCheckIfOrderExists.ts | 41 +++++++++++ src/components/Navigator/useQuickSearch.ts | 16 ++++- src/hooks/useDebounce.ts | 3 +- src/plugins/types/PluginUpdate.ts | 9 +-- src/searches/types/SearchProductTypes.ts | 8 +-- src/siteSettings/types/ShopSettingsUpdate.ts | 7 +- src/types/globalTypes.ts | 68 +++++++++---------- 11 files changed, 149 insertions(+), 60 deletions(-) create mode 100644 src/components/Navigator/modes/types.ts create mode 100644 src/components/Navigator/queries/types/CheckIfOrderExists.ts create mode 100644 src/components/Navigator/queries/useCheckIfOrderExists.ts diff --git a/src/components/Navigator/modes/index.ts b/src/components/Navigator/modes/index.ts index 2856aaeab..65164eb7d 100644 --- a/src/components/Navigator/modes/index.ts +++ b/src/components/Navigator/modes/index.ts @@ -1,17 +1,23 @@ import { IntlShape } from "react-intl"; +import { + CheckIfOrderExists, + CheckIfOrderExistsVariables +} from "../queries/types/CheckIfOrderExists"; import { QuickSearchAction, QuickSearchMode } from "../types"; import getDefaultModeActions from "./default"; import getOrdersModeActions from "./orders"; +import { ActionQueries } from "./types"; function getModeActions( mode: QuickSearchMode, query: string, - intl: IntlShape + intl: IntlShape, + queries: ActionQueries ): QuickSearchAction[] { switch (mode) { case "orders": - return getOrdersModeActions(query, intl); + return getOrdersModeActions(query, intl, queries.order); default: return getDefaultModeActions(query, intl); } diff --git a/src/components/Navigator/modes/orders.ts b/src/components/Navigator/modes/orders.ts index 09a6a8f79..4525b72f0 100644 --- a/src/components/Navigator/modes/orders.ts +++ b/src/components/Navigator/modes/orders.ts @@ -1,14 +1,27 @@ import { IntlShape } from "react-intl"; +import { maybe } 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 + intl: IntlShape, + order: CheckIfOrderExists_order ): QuickSearchAction[] { - if (query === parseInt(query, 0).toString()) { + const gqlId = getGqlOrderId(query); + + if (isQueryValidOrderNumber(query) && maybe(() => order.id === gqlId)) { return [ { label: intl.formatMessage(messages.goToOrder, { @@ -16,7 +29,7 @@ function getOrdersModeActions( }), score: 1, type: "action", - url: orderUrl(btoa(`Order:${query}`)) + url: orderUrl(gqlId) } ]; } diff --git a/src/components/Navigator/modes/types.ts b/src/components/Navigator/modes/types.ts new file mode 100644 index 000000000..ad61899c2 --- /dev/null +++ b/src/components/Navigator/modes/types.ts @@ -0,0 +1,5 @@ +import { CheckIfOrderExists_order } from "../queries/types/CheckIfOrderExists"; + +export interface ActionQueries { + order: CheckIfOrderExists_order; +} diff --git a/src/components/Navigator/queries/types/CheckIfOrderExists.ts b/src/components/Navigator/queries/types/CheckIfOrderExists.ts new file mode 100644 index 000000000..ef91ee35e --- /dev/null +++ b/src/components/Navigator/queries/types/CheckIfOrderExists.ts @@ -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; +} diff --git a/src/components/Navigator/queries/useCheckIfOrderExists.ts b/src/components/Navigator/queries/useCheckIfOrderExists.ts new file mode 100644 index 000000000..d08b65a1a --- /dev/null +++ b/src/components/Navigator/queries/useCheckIfOrderExists.ts @@ -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, + (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; diff --git a/src/components/Navigator/useQuickSearch.ts b/src/components/Navigator/useQuickSearch.ts index 3a9798341..f1b333e1a 100644 --- a/src/components/Navigator/useQuickSearch.ts +++ b/src/components/Navigator/useQuickSearch.ts @@ -3,7 +3,10 @@ import { useIntl } from "react-intl"; import { ChangeEvent, FormChange } from "@saleor/hooks/useForm"; import useModalDialogOpen from "@saleor/hooks/useModalDialogOpen"; +import { maybe } from "@saleor/misc"; import getModeActions from "./modes"; +import { getGqlOrderId, isQueryValidOrderNumber } from "./modes/orders"; +import useCheckIfOrderExists from "./queries/useCheckIfOrderExists"; import { QuickSearchAction, QuickSearchMode } from "./types"; type UseQuickSearch = [ @@ -19,6 +22,7 @@ function useQuickSearch( const [query, setQuery] = useState(""); const [mode, setMode] = useState("default"); const intl = useIntl(); + const [{ data: orderData }, getOrderData] = useCheckIfOrderExists(); useModalDialogOpen(open, { onClose: () => { @@ -60,11 +64,21 @@ function useQuickSearch( setQuery(value); } } else { + if (mode === "orders" && isQueryValidOrderNumber(value)) { + getOrderData(getGqlOrderId(value)); + } setQuery(value); } }; - return [query, mode, change, getModeActions(mode, query, intl)]; + return [ + query, + mode, + change, + getModeActions(mode, query, intl, { + order: maybe(() => orderData.order) + }) + ]; } export default useQuickSearch; diff --git a/src/hooks/useDebounce.ts b/src/hooks/useDebounce.ts index 69be1e40e..b228152f4 100644 --- a/src/hooks/useDebounce.ts +++ b/src/hooks/useDebounce.ts @@ -6,12 +6,13 @@ function useDebounce( time = 200 ): UseDebounceFn { 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); }; } diff --git a/src/plugins/types/PluginUpdate.ts b/src/plugins/types/PluginUpdate.ts index 1a5bb77a2..49a2d0b9e 100644 --- a/src/plugins/types/PluginUpdate.ts +++ b/src/plugins/types/PluginUpdate.ts @@ -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 { diff --git a/src/searches/types/SearchProductTypes.ts b/src/searches/types/SearchProductTypes.ts index 300ebc329..a35124ca1 100644 --- a/src/searches/types/SearchProductTypes.ts +++ b/src/searches/types/SearchProductTypes.ts @@ -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 { diff --git a/src/siteSettings/types/ShopSettingsUpdate.ts b/src/siteSettings/types/ShopSettingsUpdate.ts index 0a9b046a4..2b8419636 100644 --- a/src/siteSettings/types/ShopSettingsUpdate.ts +++ b/src/siteSettings/types/ShopSettingsUpdate.ts @@ -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 diff --git a/src/types/globalTypes.ts b/src/types/globalTypes.ts index 10b35084d..44ec52260 100644 --- a/src/types/globalTypes.ts +++ b/src/types/globalTypes.ts @@ -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 { From e6dfd5c3e579f6310e16714e6ecd5a9e032d2eae Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 22 Nov 2019 16:39:20 +0100 Subject: [PATCH 06/22] Add command mode --- src/components/Navigator/Navigator.tsx | 31 ++++---- src/components/Navigator/NavigatorInput.tsx | 11 ++- .../Navigator/modes/commands/actions.ts | 75 +++++++++++++++++++ .../Navigator/modes/commands/index.ts | 2 + .../Navigator/modes/default/actions.ts | 8 -- .../Navigator/modes/default/default.ts | 15 +++- .../Navigator/modes/default/index.ts | 1 - .../Navigator/modes/default/views.ts | 18 ++--- src/components/Navigator/modes/index.ts | 20 +++-- src/components/Navigator/modes/messages.ts | 24 ++++++ src/components/Navigator/modes/orders.ts | 6 +- src/components/Navigator/modes/utils.ts | 15 ++++ src/components/Navigator/types.ts | 4 +- src/components/Navigator/useQuickSearch.ts | 30 +++++++- src/hooks/makeMutation.ts | 6 +- src/orders/mutations.ts | 8 +- 16 files changed, 213 insertions(+), 61 deletions(-) create mode 100644 src/components/Navigator/modes/commands/actions.ts create mode 100644 src/components/Navigator/modes/commands/index.ts delete mode 100644 src/components/Navigator/modes/default/actions.ts create mode 100644 src/components/Navigator/modes/utils.ts diff --git a/src/components/Navigator/Navigator.tsx b/src/components/Navigator/Navigator.tsx index d58a0cd1b..ea2b8510c 100644 --- a/src/components/Navigator/Navigator.tsx +++ b/src/components/Navigator/Navigator.tsx @@ -4,9 +4,7 @@ import hotkeys from "hotkeys-js"; import React from "react"; import { useIntl } from "react-intl"; -import useNavigator from "@saleor/hooks/useNavigator"; -import { getActions, hasActions } from "./modes/default"; -import { getViews, hasViews } from "./modes/default/views"; +import { getActions, getViews, hasActions, hasViews } from "./modes/utils"; import NavigatorInput from "./NavigatorInput"; import NavigatorSection from "./NavigatorSection"; import { QuickSearchAction } from "./types"; @@ -18,7 +16,6 @@ const Navigator: React.FC = () => { const [visible, setVisible] = React.useState(false); const input = React.useRef(null); const [query, mode, change, actions] = useQuickSearch(visible, input); - const navigate = useNavigator(); const intl = useIntl(); React.useEffect(() => { @@ -37,8 +34,8 @@ const Navigator: React.FC = () => { (item ? item.label : "")} onSelect={(item: QuickSearchAction) => { - navigate(item.url); setVisible(false); + item.onClick(); }} onInputValueChange={value => change({ @@ -60,18 +57,6 @@ const Navigator: React.FC = () => { })} ref={input} /> - {hasActions(actions) && ( - - )} {hasViews(actions) && ( { offset={getActions(actions).length} /> )} + {hasActions(actions) && ( + + )} )} diff --git a/src/components/Navigator/NavigatorInput.tsx b/src/components/Navigator/NavigatorInput.tsx index fcb7907f9..f32c90f9b 100644 --- a/src/components/Navigator/NavigatorInput.tsx +++ b/src/components/Navigator/NavigatorInput.tsx @@ -50,7 +50,11 @@ const NavigatorInput = React.forwardRef( return (
- {mode === "orders" && #} + {mode !== "default" && ( + + {mode === "orders" ? "#" : ">"} + + )} ( defaultMessage: "Order Number", description: "navigator placeholder" }) + : mode === "commands" + ? intl.formatMessage({ + defaultMessage: "Type Command", + description: "navigator placeholder" + }) : intl.formatMessage({ defaultMessage: "Use Navigator to move through Saleor", description: "navigator placeholder" diff --git a/src/components/Navigator/modes/commands/actions.ts b/src/components/Navigator/modes/commands/actions.ts new file mode 100644 index 000000000..2d00ab69d --- /dev/null +++ b/src/components/Navigator/modes/commands/actions.ts @@ -0,0 +1,75 @@ +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 { QuickSearchAction } from "../../types"; +import messages from "../messages"; + +const threshold = 0.05; +const maxActions = 5; + +interface Command { + label: string; + onClick: () => void; +} +export function searchInCommands( + search: string, + intl: IntlShape, + navigate: UseNavigatorResult, + createOrder: MutationFunction +): QuickSearchAction[] { + const actions: Command[] = [ + { + label: intl.formatMessage(messages.addCategory), + onClick: () => navigate(categoryAddUrl()) + }, + { + label: intl.formatMessage(messages.addCollection), + onClick: () => navigate(collectionAddUrl) + }, + { + label: intl.formatMessage(messages.addProduct), + onClick: () => navigate(productAddUrl) + }, + { + label: intl.formatMessage(messages.addCustomer), + onClick: () => navigate(customerAddUrl) + }, + { + label: intl.formatMessage(messages.addVoucher), + onClick: () => navigate(voucherAddUrl) + }, + { + label: intl.formatMessage(messages.createOrder), + onClick: createOrder + } + ]; + + return actions.map(action => ({ + label: action.label, + onClick: action.onClick, + score: score(action.label, search), + type: "action" + })); +} + +function getCommandModeActions( + query: string, + intl: IntlShape, + navigate: UseNavigatorResult, + createOrder: MutationFunction +): QuickSearchAction[] { + return [...searchInCommands(query, intl, navigate, createOrder)] + .filter(action => action.score >= threshold) + .sort((a, b) => (a.score <= b.score ? 1 : -1)) + .slice(0, maxActions); +} + +export default getCommandModeActions; diff --git a/src/components/Navigator/modes/commands/index.ts b/src/components/Navigator/modes/commands/index.ts new file mode 100644 index 000000000..9600b6455 --- /dev/null +++ b/src/components/Navigator/modes/commands/index.ts @@ -0,0 +1,2 @@ +export * from "./actions"; +export { default } from "./actions"; diff --git a/src/components/Navigator/modes/default/actions.ts b/src/components/Navigator/modes/default/actions.ts deleted file mode 100644 index ee7c489bd..000000000 --- a/src/components/Navigator/modes/default/actions.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { QuickSearchAction } 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; -} diff --git a/src/components/Navigator/modes/default/default.ts b/src/components/Navigator/modes/default/default.ts index 41f1c3e09..d56a44e79 100644 --- a/src/components/Navigator/modes/default/default.ts +++ b/src/components/Navigator/modes/default/default.ts @@ -1,16 +1,25 @@ +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 } from "../../types"; +import { searchInCommands } from "../commands"; import searchInViews from "./views"; const threshold = 0.05; -const maxActions = 10; +const maxActions = 5; function getDefaultModeActions( query: string, - intl: IntlShape + intl: IntlShape, + navigate: UseNavigatorResult, + createOrder: MutationFunction ): QuickSearchAction[] { - return [...searchInViews(query, intl)] + return [ + ...searchInViews(query, intl, navigate), + ...searchInCommands(query, intl, navigate, createOrder) + ] .filter(action => action.score >= threshold) .sort((a, b) => (a.score <= b.score ? 1 : -1)) .slice(0, maxActions); diff --git a/src/components/Navigator/modes/default/index.ts b/src/components/Navigator/modes/default/index.ts index 445c4ca3d..86fa704d4 100644 --- a/src/components/Navigator/modes/default/index.ts +++ b/src/components/Navigator/modes/default/index.ts @@ -1,4 +1,3 @@ -export * from "./actions"; export * from "./default"; export { default } from "./default"; export * from "./views"; diff --git a/src/components/Navigator/modes/default/views.ts b/src/components/Navigator/modes/default/views.ts index 8b6819967..6ae6980f0 100644 --- a/src/components/Navigator/modes/default/views.ts +++ b/src/components/Navigator/modes/default/views.ts @@ -6,6 +6,7 @@ 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"; @@ -26,7 +27,11 @@ interface View { label: string; url: string; } -function searchInViews(search: string, intl: IntlShape): QuickSearchAction[] { +function searchInViews( + search: string, + intl: IntlShape, + navigate: UseNavigatorResult +): QuickSearchAction[] { const views: View[] = [ { label: intl.formatMessage(sectionNames.attributes), @@ -116,17 +121,10 @@ function searchInViews(search: string, intl: IntlShape): QuickSearchAction[] { return views.map(view => ({ label: view.label, + onClick: () => navigate(view.url), score: score(view.label, search), - type: "view", - url: view.url + type: "view" })); } -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 default searchInViews; diff --git a/src/components/Navigator/modes/index.ts b/src/components/Navigator/modes/index.ts index 65164eb7d..4bd08f23d 100644 --- a/src/components/Navigator/modes/index.ts +++ b/src/components/Navigator/modes/index.ts @@ -1,10 +1,10 @@ import { IntlShape } from "react-intl"; -import { - CheckIfOrderExists, - CheckIfOrderExistsVariables -} from "../queries/types/CheckIfOrderExists"; +import { UseNavigatorResult } from "@saleor/hooks/useNavigator"; +import { OrderDraftCreate } from "@saleor/orders/types/OrderDraftCreate"; +import { MutationFunction } from "react-apollo"; import { QuickSearchAction, QuickSearchMode } from "../types"; +import getCommandModeActions from "./commands"; import getDefaultModeActions from "./default"; import getOrdersModeActions from "./orders"; import { ActionQueries } from "./types"; @@ -13,13 +13,19 @@ function getModeActions( mode: QuickSearchMode, query: string, intl: IntlShape, - queries: ActionQueries + queries: ActionQueries, + cbs: { + navigate: UseNavigatorResult; + createOrder: MutationFunction; + } ): QuickSearchAction[] { switch (mode) { + case "commands": + return getCommandModeActions(query, intl, cbs.navigate, cbs.createOrder); case "orders": - return getOrdersModeActions(query, intl, queries.order); + return getOrdersModeActions(query, intl, cbs.navigate, queries.order); default: - return getDefaultModeActions(query, intl); + return getDefaultModeActions(query, intl, cbs.navigate, cbs.createOrder); } } diff --git a/src/components/Navigator/modes/messages.ts b/src/components/Navigator/modes/messages.ts index 0fce6090f..07c6df3d0 100644 --- a/src/components/Navigator/modes/messages.ts +++ b/src/components/Navigator/modes/messages.ts @@ -1,6 +1,30 @@ import { defineMessages } from "react-intl"; const messages = defineMessages({ + addCategory: { + defaultMessage: "Add Category", + description: "button" + }, + addCollection: { + defaultMessage: "Add Collection", + description: "button" + }, + addCustomer: { + defaultMessage: "Add Customer", + description: "button" + }, + addProduct: { + defaultMessage: "Add Product", + description: "button" + }, + addVoucher: { + defaultMessage: "Add Voucher", + description: "button" + }, + createOrder: { + defaultMessage: "Create Order", + description: "button" + }, goToOrder: { defaultMessage: "Go to order #{orderNumber}", description: "navigator action" diff --git a/src/components/Navigator/modes/orders.ts b/src/components/Navigator/modes/orders.ts index 4525b72f0..0156d68c6 100644 --- a/src/components/Navigator/modes/orders.ts +++ b/src/components/Navigator/modes/orders.ts @@ -1,5 +1,6 @@ import { IntlShape } from "react-intl"; +import { UseNavigatorResult } from "@saleor/hooks/useNavigator"; import { maybe } from "@saleor/misc"; import { orderUrl } from "@saleor/orders/urls"; import { CheckIfOrderExists_order } from "../queries/types/CheckIfOrderExists"; @@ -17,6 +18,7 @@ export function getGqlOrderId(orderNumber: string): string { function getOrdersModeActions( query: string, intl: IntlShape, + navigate: UseNavigatorResult, order: CheckIfOrderExists_order ): QuickSearchAction[] { const gqlId = getGqlOrderId(query); @@ -27,9 +29,9 @@ function getOrdersModeActions( label: intl.formatMessage(messages.goToOrder, { orderNumber: query }), + onClick: () => navigate(orderUrl(gqlId)), score: 1, - type: "action", - url: orderUrl(gqlId) + type: "action" } ]; } diff --git a/src/components/Navigator/modes/utils.ts b/src/components/Navigator/modes/utils.ts new file mode 100644 index 000000000..ceabc81ca --- /dev/null +++ b/src/components/Navigator/modes/utils.ts @@ -0,0 +1,15 @@ +import { QuickSearchAction } 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; +} diff --git a/src/components/Navigator/types.ts b/src/components/Navigator/types.ts index 3646a7ce9..12279daf5 100644 --- a/src/components/Navigator/types.ts +++ b/src/components/Navigator/types.ts @@ -4,7 +4,7 @@ export interface QuickSearchAction { label: string; score: number; type: QuickSearchActionType; - url: string; + onClick: () => void; } -export type QuickSearchMode = "default" | "orders" | "customers"; +export type QuickSearchMode = "default" | "commands" | "orders" | "customers"; diff --git a/src/components/Navigator/useQuickSearch.ts b/src/components/Navigator/useQuickSearch.ts index f1b333e1a..fee527cf5 100644 --- a/src/components/Navigator/useQuickSearch.ts +++ b/src/components/Navigator/useQuickSearch.ts @@ -3,7 +3,10 @@ import { useIntl } from "react-intl"; 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 getModeActions from "./modes"; import { getGqlOrderId, isQueryValidOrderNumber } from "./modes/orders"; import useCheckIfOrderExists from "./queries/useCheckIfOrderExists"; @@ -22,7 +25,15 @@ function useQuickSearch( const [query, setQuery] = useState(""); const [mode, setMode] = useState("default"); const intl = useIntl(); + const navigate = useNavigator(); const [{ data: orderData }, getOrderData] = useCheckIfOrderExists(); + const [createOrder] = useOrderDraftCreateMutation({ + onCompleted: result => { + if (result.draftOrderCreate.errors.length === 0) { + navigate(orderUrl(result.draftOrderCreate.order.id)); + } + } + }); useModalDialogOpen(open, { onClose: () => { @@ -58,8 +69,12 @@ function useQuickSearch( if (mode === "default") { switch (value) { + case "> ": + setMode("commands"); + break; case "# ": setMode("orders"); + break; default: setQuery(value); } @@ -75,9 +90,18 @@ function useQuickSearch( query, mode, change, - getModeActions(mode, query, intl, { - order: maybe(() => orderData.order) - }) + getModeActions( + mode, + query, + intl, + { + order: maybe(() => orderData.order) + }, + { + createOrder, + navigate + } + ) ]; } diff --git a/src/hooks/makeMutation.ts b/src/hooks/makeMutation.ts index b6cfd4a91..0ec08736c 100644 --- a/src/hooks/makeMutation.ts +++ b/src/hooks/makeMutation.ts @@ -11,15 +11,15 @@ import { commonMessages } from "@saleor/intl"; import { maybe } from "@saleor/misc"; import useNotifier from "./useNotifier"; -type UseMutation = [ +export type UseMutation = [ MutationFunction, MutationResult ]; -type UseMutationCbs = Partial<{ +export type UseMutationCbs = Partial<{ onCompleted: (data: TData) => void; onError: (error: ApolloError) => void; }>; -type UseMutationHook = ( +export type UseMutationHook = ( cbs: UseMutationCbs ) => UseMutation; diff --git a/src/orders/mutations.ts b/src/orders/mutations.ts index 8ab8f659b..4a68ab6ba 100644 --- a/src/orders/mutations.ts +++ b/src/orders/mutations.ts @@ -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( + orderDraftCreateMutation +); const orderLineDeleteMutation = gql` ${fragmentOrderDetails} From 340e00f030290839411404913b4fc7799898e97a Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 22 Nov 2019 16:52:19 +0100 Subject: [PATCH 07/22] Fix types --- .../views/OrderDraftList/OrderDraftList.tsx | 273 +++++++++--------- src/orders/views/OrderList/OrderList.tsx | 264 ++++++++--------- 2 files changed, 267 insertions(+), 270 deletions(-) diff --git a/src/orders/views/OrderDraftList/OrderDraftList.tsx b/src/orders/views/OrderDraftList/OrderDraftList.tsx index bbda0756e..f9b1ad9a7 100644 --- a/src/orders/views/OrderDraftList/OrderDraftList.tsx +++ b/src/orders/views/OrderDraftList/OrderDraftList.tsx @@ -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 = ({ 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 = ({ 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 = ({ params }) => { ); return ( - - {createOrder => ( - - {({ data, loading, refetch }) => { - const { loadNextPage, loadPreviousPage, pageInfo } = paginate( - maybe(() => data.draftOrders.pageInfo), - paginationState, - params - ); + + {({ 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 ( + + {(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 ( - - {(orderDraftBulkDelete, orderDraftBulkDeleteOpts) => { - const bulkRemoveTransitionState = getMutationState( - orderDraftBulkDeleteOpts.called, - orderDraftBulkDeleteOpts.loading, - maybe( - () => - orderDraftBulkDeleteOpts.data.draftOrderBulkDelete - .errors - ) - ); - const onOrderDraftBulkDelete = () => - orderDraftBulkDelete({ - variables: { - ids: params.ids - } - }); - - return ( - <> - 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={ - - navigate( - orderDraftListUrl({ - action: "remove", - ids: listElements - }) - ) - } - > - - + return ( + <> + 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={ + + navigate( + orderDraftListUrl({ + action: "remove", + ids: listElements + }) + ) } - /> - - - params.ids.length), - displayQuantity: ( - - {maybe(() => params.ids.length)} - - ) - }} - /> - - - + + } + /> + + + params.ids.length), + displayQuantity: ( + {maybe(() => params.ids.length)} + ) + }} /> - tabs[currentTab - 1].name, "...")} - /> - - ); - }} - - ); - }} - - )} - + + + + tabs[currentTab - 1].name, "...")} + /> + + ); + }} + + ); + }} + ); }; diff --git a/src/orders/views/OrderList/OrderList.tsx b/src/orders/views/OrderList/OrderList.tsx index 26e49fd22..e5b19e27f 100644 --- a/src/orders/views/OrderList/OrderList.tsx +++ b/src/orders/views/OrderList/OrderList.tsx @@ -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 = ({ 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 = ({ 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 = ({ params }) => { ); return ( - - {createOrder => ( - - {({ data, loading, refetch }) => { - const { loadNextPage, loadPreviousPage, pageInfo } = paginate( - maybe(() => data.orders.pageInfo), - paginationState, - params - ); + + {({ 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 ( + + {(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 ( - - {(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 ( - <> - - 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={ - - } - 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 - }) - } - /> - params.ids.length.toString(), - "..." - )} - onClose={closeModal} - onConfirm={onOrderBulkCancel} - open={params.action === "cancel"} - /> - - tabs[currentTab - 1].name, "...")} - /> - - ); - }} - - ); - }} - - )} - + return ( + <> + + 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={ + + } + 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 + }) + } + /> + params.ids.length.toString(), + "..." + )} + onClose={closeModal} + onConfirm={onOrderBulkCancel} + open={params.action === "cancel"} + /> + + tabs[currentTab - 1].name, "...")} + /> + + ); + }} + + ); + }} + ); }; From ebf386dfb54c72dc1caed429fc1e178f61e94583 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 22 Nov 2019 17:07:16 +0100 Subject: [PATCH 08/22] Fix action ordering --- src/components/Navigator/Navigator.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Navigator/Navigator.tsx b/src/components/Navigator/Navigator.tsx index ea2b8510c..c8cd11f83 100644 --- a/src/components/Navigator/Navigator.tsx +++ b/src/components/Navigator/Navigator.tsx @@ -66,7 +66,7 @@ const Navigator: React.FC = () => { getItemProps={getItemProps} highlightedIndex={highlightedIndex} items={getViews(actions)} - offset={getActions(actions).length} + offset={0} /> )} {hasActions(actions) && ( @@ -78,7 +78,7 @@ const Navigator: React.FC = () => { getItemProps={getItemProps} highlightedIndex={highlightedIndex} items={getActions(actions)} - offset={0} + offset={getViews(actions).length} /> )}
From 42b55e860b4dd5c2bbcedd4393e9ac49da76a3f7 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 25 Nov 2019 12:29:07 +0100 Subject: [PATCH 09/22] Add customer search --- src/components/Navigator/Navigator.tsx | 30 ++++++++++++++-- src/components/Navigator/NavigatorInput.tsx | 2 +- src/components/Navigator/modes/customers.ts | 36 +++++++++++++++++++ .../Navigator/modes/default/default.ts | 11 +++++- src/components/Navigator/modes/index.ts | 11 +++++- src/components/Navigator/modes/messages.ts | 3 ++ src/components/Navigator/modes/types.ts | 2 ++ src/components/Navigator/modes/utils.ts | 9 +++++ src/components/Navigator/types.ts | 2 +- src/components/Navigator/useQuickSearch.ts | 19 ++++++++++ src/orders/fixtures.ts | 16 ++++++--- src/searches/types/SearchCustomers.ts | 2 ++ src/searches/useCustomerSearch.ts | 2 ++ 13 files changed, 135 insertions(+), 10 deletions(-) create mode 100644 src/components/Navigator/modes/customers.ts diff --git a/src/components/Navigator/Navigator.tsx b/src/components/Navigator/Navigator.tsx index c8cd11f83..0614b2888 100644 --- a/src/components/Navigator/Navigator.tsx +++ b/src/components/Navigator/Navigator.tsx @@ -4,7 +4,14 @@ import hotkeys from "hotkeys-js"; import React from "react"; import { useIntl } from "react-intl"; -import { getActions, getViews, hasActions, hasViews } from "./modes/utils"; +import { + getActions, + getCustomers, + getViews, + hasActions, + hasCustomers, + hasViews +} from "./modes/utils"; import NavigatorInput from "./NavigatorInput"; import NavigatorSection from "./NavigatorSection"; import { QuickSearchAction } from "./types"; @@ -12,6 +19,13 @@ import useQuickSearch from "./useQuickSearch"; const navigatorHotkey = "ctrl+m, command+m"; +function getItemOffset( + actions: QuickSearchAction[], + cbs: Array +): number { + return cbs.reduce((acc, cb) => cb(actions).length + acc, 0); +} + const Navigator: React.FC = () => { const [visible, setVisible] = React.useState(false); const input = React.useRef(null); @@ -78,7 +92,19 @@ const Navigator: React.FC = () => { getItemProps={getItemProps} highlightedIndex={highlightedIndex} items={getActions(actions)} - offset={getViews(actions).length} + offset={getItemOffset(actions, [getViews])} + /> + )} + {hasCustomers(actions) && ( + )} diff --git a/src/components/Navigator/NavigatorInput.tsx b/src/components/Navigator/NavigatorInput.tsx index f32c90f9b..6a3156cb1 100644 --- a/src/components/Navigator/NavigatorInput.tsx +++ b/src/components/Navigator/NavigatorInput.tsx @@ -52,7 +52,7 @@ const NavigatorInput = React.forwardRef(
{mode !== "default" && ( - {mode === "orders" ? "#" : ">"} + {mode === "orders" ? "#" : mode === "customers" ? "@" : ">"} )} ({ + label: + customer.firstName && customer.lastName + ? intl.formatMessage(messages.customerWithName, { + firstName: customer.firstName, + lastName: customer.lastName + }) + : customer.email, + onClick: () => navigate(customerUrl(customer.id)), + score: 1, + type: "customer" + })); +} + +function getCustomersModeActions( + intl: IntlShape, + navigate: UseNavigatorResult, + customers: SearchCustomers_search_edges_node[] +): QuickSearchAction[] { + return searchInCustomers(intl, navigate, customers); +} + +export default getCustomersModeActions; diff --git a/src/components/Navigator/modes/default/default.ts b/src/components/Navigator/modes/default/default.ts index d56a44e79..8ed15741a 100644 --- a/src/components/Navigator/modes/default/default.ts +++ b/src/components/Navigator/modes/default/default.ts @@ -3,8 +3,10 @@ import { IntlShape } from "react-intl"; import { UseNavigatorResult } from "@saleor/hooks/useNavigator"; import { OrderDraftCreate } from "@saleor/orders/types/OrderDraftCreate"; +import { SearchCustomers_search_edges_node } from "@saleor/searches/types/SearchCustomers"; import { QuickSearchAction } from "../../types"; import { searchInCommands } from "../commands"; +import { searchInCustomers } from "../customers"; import searchInViews from "./views"; const threshold = 0.05; @@ -14,15 +16,22 @@ function getDefaultModeActions( query: string, intl: IntlShape, navigate: UseNavigatorResult, + customers: SearchCustomers_search_edges_node[], createOrder: MutationFunction ): QuickSearchAction[] { - return [ + const actions = [ ...searchInViews(query, intl, navigate), ...searchInCommands(query, intl, navigate, createOrder) ] .filter(action => action.score >= threshold) .sort((a, b) => (a.score <= b.score ? 1 : -1)) .slice(0, maxActions); + + if (query !== "") { + return [...actions, ...searchInCustomers(intl, navigate, customers)]; + } + + return actions; } export default getDefaultModeActions; diff --git a/src/components/Navigator/modes/index.ts b/src/components/Navigator/modes/index.ts index 4bd08f23d..c26d4c8bf 100644 --- a/src/components/Navigator/modes/index.ts +++ b/src/components/Navigator/modes/index.ts @@ -5,6 +5,7 @@ import { OrderDraftCreate } from "@saleor/orders/types/OrderDraftCreate"; import { MutationFunction } from "react-apollo"; import { QuickSearchAction, QuickSearchMode } from "../types"; import getCommandModeActions from "./commands"; +import getCustomersModeActions from "./customers"; import getDefaultModeActions from "./default"; import getOrdersModeActions from "./orders"; import { ActionQueries } from "./types"; @@ -22,10 +23,18 @@ function getModeActions( switch (mode) { case "commands": return getCommandModeActions(query, intl, cbs.navigate, cbs.createOrder); + case "customers": + return getCustomersModeActions(intl, cbs.navigate, queries.customers); case "orders": return getOrdersModeActions(query, intl, cbs.navigate, queries.order); default: - return getDefaultModeActions(query, intl, cbs.navigate, cbs.createOrder); + return getDefaultModeActions( + query, + intl, + cbs.navigate, + queries.customers, + cbs.createOrder + ); } } diff --git a/src/components/Navigator/modes/messages.ts b/src/components/Navigator/modes/messages.ts index 07c6df3d0..edeef81b0 100644 --- a/src/components/Navigator/modes/messages.ts +++ b/src/components/Navigator/modes/messages.ts @@ -25,6 +25,9 @@ const messages = defineMessages({ defaultMessage: "Create Order", description: "button" }, + customerWithName: { + defaultMessage: "{firstName} {lastName}" + }, goToOrder: { defaultMessage: "Go to order #{orderNumber}", description: "navigator action" diff --git a/src/components/Navigator/modes/types.ts b/src/components/Navigator/modes/types.ts index ad61899c2..b207d7e35 100644 --- a/src/components/Navigator/modes/types.ts +++ b/src/components/Navigator/modes/types.ts @@ -1,5 +1,7 @@ +import { SearchCustomers_search_edges_node } from "@saleor/searches/types/SearchCustomers"; import { CheckIfOrderExists_order } from "../queries/types/CheckIfOrderExists"; export interface ActionQueries { + customers: SearchCustomers_search_edges_node[]; order: CheckIfOrderExists_order; } diff --git a/src/components/Navigator/modes/utils.ts b/src/components/Navigator/modes/utils.ts index ceabc81ca..c625f0ddd 100644 --- a/src/components/Navigator/modes/utils.ts +++ b/src/components/Navigator/modes/utils.ts @@ -13,3 +13,12 @@ export function getViews(actions: QuickSearchAction[]): QuickSearchAction[] { 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; +} diff --git a/src/components/Navigator/types.ts b/src/components/Navigator/types.ts index 12279daf5..ddfab5999 100644 --- a/src/components/Navigator/types.ts +++ b/src/components/Navigator/types.ts @@ -1,4 +1,4 @@ -export type QuickSearchActionType = "action" | "view"; +export type QuickSearchActionType = "action" | "customer" | "view"; export interface QuickSearchAction { label: string; diff --git a/src/components/Navigator/useQuickSearch.ts b/src/components/Navigator/useQuickSearch.ts index fee527cf5..316540fbb 100644 --- a/src/components/Navigator/useQuickSearch.ts +++ b/src/components/Navigator/useQuickSearch.ts @@ -1,12 +1,14 @@ 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 useCheckIfOrderExists from "./queries/useCheckIfOrderExists"; @@ -27,6 +29,12 @@ function useQuickSearch( 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 [createOrder] = useOrderDraftCreateMutation({ onCompleted: result => { if (result.draftOrderCreate.errors.length === 0) { @@ -72,6 +80,9 @@ function useQuickSearch( case "> ": setMode("commands"); break; + case "@ ": + setMode("customers"); + break; case "# ": setMode("orders"); break; @@ -84,6 +95,10 @@ function useQuickSearch( } setQuery(value); } + + if ((["customers", "default"] as QuickSearchMode[]).includes(mode)) { + searchCustomers(value); + } }; return [ @@ -95,6 +110,10 @@ function useQuickSearch( query, intl, { + customers: maybe( + () => customers.data.search.edges.map(edge => edge.node), + [] + ), order: maybe(() => orderData.order) }, { diff --git a/src/orders/fixtures.ts b/src/orders/fixtures.ts index 4463ec60c..754d8628d 100644 --- a/src/orders/fixtures.ts +++ b/src/orders/fixtures.ts @@ -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[] = [ diff --git a/src/searches/types/SearchCustomers.ts b/src/searches/types/SearchCustomers.ts index c63a1a4b6..37c864c0b 100644 --- a/src/searches/types/SearchCustomers.ts +++ b/src/searches/types/SearchCustomers.ts @@ -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 { diff --git a/src/searches/useCustomerSearch.ts b/src/searches/useCustomerSearch.ts index bd046f0ab..92f4165d9 100644 --- a/src/searches/useCustomerSearch.ts +++ b/src/searches/useCustomerSearch.ts @@ -15,6 +15,8 @@ export const searchCustomers = gql` node { id email + firstName + lastName } } pageInfo { From 649f35e0d12b93e7c711c18a2c7cf5fab98a2a6b Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 25 Nov 2019 14:51:46 +0100 Subject: [PATCH 10/22] Improve labels --- src/components/Navigator/NavigatorSection.tsx | 16 +++++++++++++++- .../Navigator/modes/commands/actions.ts | 7 ++++--- src/components/Navigator/modes/customers.ts | 1 + src/components/Navigator/modes/default/views.ts | 5 +++-- src/components/Navigator/modes/orders.ts | 4 ++-- src/components/Navigator/types.ts | 9 ++++++++- 6 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/components/Navigator/NavigatorSection.tsx b/src/components/Navigator/NavigatorSection.tsx index c4c5b0623..69f7eb603 100644 --- a/src/components/Navigator/NavigatorSection.tsx +++ b/src/components/Navigator/NavigatorSection.tsx @@ -22,8 +22,12 @@ const useStyles = makeStyles( 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" @@ -34,6 +38,9 @@ const useStyles = makeStyles( }, margin: theme.spacing(2, 0), padding: theme.spacing(0, 1) + }, + spacer: { + flex: 1 } }), { @@ -70,7 +77,14 @@ const NavigatorSection: React.FC = props => { selected={highlightedIndex === index} key={[item.label, item.type].join(":")} > - {item.label} + + {item.label} + {item.caption && ( + {item.caption} + )} + + + {item.extraInfo} ); })} diff --git a/src/components/Navigator/modes/commands/actions.ts b/src/components/Navigator/modes/commands/actions.ts index 2d00ab69d..4f2bc1d23 100644 --- a/src/components/Navigator/modes/commands/actions.ts +++ b/src/components/Navigator/modes/commands/actions.ts @@ -9,7 +9,7 @@ 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 { QuickSearchAction } from "../../types"; +import { QuickSearchActionInput } from "../../types"; import messages from "../messages"; const threshold = 0.05; @@ -24,7 +24,7 @@ export function searchInCommands( intl: IntlShape, navigate: UseNavigatorResult, createOrder: MutationFunction -): QuickSearchAction[] { +): QuickSearchActionInput[] { const actions: Command[] = [ { label: intl.formatMessage(messages.addCategory), @@ -56,6 +56,7 @@ export function searchInCommands( label: action.label, onClick: action.onClick, score: score(action.label, search), + text: action.label, type: "action" })); } @@ -65,7 +66,7 @@ function getCommandModeActions( intl: IntlShape, navigate: UseNavigatorResult, createOrder: MutationFunction -): QuickSearchAction[] { +): QuickSearchActionInput[] { return [...searchInCommands(query, intl, navigate, createOrder)] .filter(action => action.score >= threshold) .sort((a, b) => (a.score <= b.score ? 1 : -1)) diff --git a/src/components/Navigator/modes/customers.ts b/src/components/Navigator/modes/customers.ts index 9efadd43f..4a7e8d7c0 100644 --- a/src/components/Navigator/modes/customers.ts +++ b/src/components/Navigator/modes/customers.ts @@ -12,6 +12,7 @@ export function searchInCustomers( customers: SearchCustomers_search_edges_node[] ): QuickSearchAction[] { return customers.map(customer => ({ + caption: customer.email, label: customer.firstName && customer.lastName ? intl.formatMessage(messages.customerWithName, { diff --git a/src/components/Navigator/modes/default/views.ts b/src/components/Navigator/modes/default/views.ts index 6ae6980f0..dede14220 100644 --- a/src/components/Navigator/modes/default/views.ts +++ b/src/components/Navigator/modes/default/views.ts @@ -21,7 +21,7 @@ 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 { QuickSearchAction } from "../../types"; +import { QuickSearchActionInput } from "../../types"; interface View { label: string; @@ -31,7 +31,7 @@ function searchInViews( search: string, intl: IntlShape, navigate: UseNavigatorResult -): QuickSearchAction[] { +): QuickSearchActionInput[] { const views: View[] = [ { label: intl.formatMessage(sectionNames.attributes), @@ -123,6 +123,7 @@ function searchInViews( label: view.label, onClick: () => navigate(view.url), score: score(view.label, search), + text: view.label, type: "view" })); } diff --git a/src/components/Navigator/modes/orders.ts b/src/components/Navigator/modes/orders.ts index 0156d68c6..d4e3d7da2 100644 --- a/src/components/Navigator/modes/orders.ts +++ b/src/components/Navigator/modes/orders.ts @@ -1,7 +1,7 @@ import { IntlShape } from "react-intl"; import { UseNavigatorResult } from "@saleor/hooks/useNavigator"; -import { maybe } from "@saleor/misc"; +import { maybe, transformOrderStatus } from "@saleor/misc"; import { orderUrl } from "@saleor/orders/urls"; import { CheckIfOrderExists_order } from "../queries/types/CheckIfOrderExists"; import { QuickSearchAction } from "../types"; @@ -26,11 +26,11 @@ function getOrdersModeActions( 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)), - score: 1, type: "action" } ]; diff --git a/src/components/Navigator/types.ts b/src/components/Navigator/types.ts index ddfab5999..6a9a505e1 100644 --- a/src/components/Navigator/types.ts +++ b/src/components/Navigator/types.ts @@ -1,10 +1,17 @@ export type QuickSearchActionType = "action" | "customer" | "view"; export interface QuickSearchAction { + caption?: string; + extraInfo?: string; label: string; - score: number; + price?: number; type: QuickSearchActionType; onClick: () => void; } +export interface QuickSearchActionInput extends QuickSearchAction { + score: number; + text: string; +} + export type QuickSearchMode = "default" | "commands" | "orders" | "customers"; From 4fe0b6da65b13970862187d0818f1465817b9894 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 25 Nov 2019 15:32:10 +0100 Subject: [PATCH 11/22] Add basic catalog search --- src/components/Navigator/Navigator.tsx | 14 ++++ src/components/Navigator/NavigatorInput.tsx | 8 +- src/components/Navigator/modes/catalog.ts | 70 +++++++++++++++++ src/components/Navigator/modes/index.ts | 3 + src/components/Navigator/modes/messages.ts | 12 +++ src/components/Navigator/modes/types.ts | 2 + src/components/Navigator/modes/utils.ts | 7 ++ .../Navigator/queries/types/SearchCatalog.ts | 75 +++++++++++++++++++ .../Navigator/queries/useCatalogSearch.ts | 66 ++++++++++++++++ src/components/Navigator/types.ts | 9 ++- src/components/Navigator/useQuickSearch.ts | 9 +++ 11 files changed, 272 insertions(+), 3 deletions(-) create mode 100644 src/components/Navigator/modes/catalog.ts create mode 100644 src/components/Navigator/queries/types/SearchCatalog.ts create mode 100644 src/components/Navigator/queries/useCatalogSearch.ts diff --git a/src/components/Navigator/Navigator.tsx b/src/components/Navigator/Navigator.tsx index 0614b2888..90eb55ff1 100644 --- a/src/components/Navigator/Navigator.tsx +++ b/src/components/Navigator/Navigator.tsx @@ -6,9 +6,11 @@ import { useIntl } from "react-intl"; import { getActions, + getCatalog, getCustomers, getViews, hasActions, + hasCatalog, hasCustomers, hasViews } from "./modes/utils"; @@ -107,6 +109,18 @@ const Navigator: React.FC = () => { offset={getItemOffset(actions, [getViews, getActions])} /> )} + {hasCatalog(actions) && ( + + )}
)} diff --git a/src/components/Navigator/NavigatorInput.tsx b/src/components/Navigator/NavigatorInput.tsx index 6a3156cb1..74961502b 100644 --- a/src/components/Navigator/NavigatorInput.tsx +++ b/src/components/Navigator/NavigatorInput.tsx @@ -52,7 +52,13 @@ const NavigatorInput = React.forwardRef(
{mode !== "default" && ( - {mode === "orders" ? "#" : mode === "customers" ? "@" : ">"} + {mode === "orders" + ? "#" + : mode === "customers" + ? "@" + : mode === "catalog" + ? "$" + : ">"} )} catalog.categories.edges.map(edge => edge.node), + [] + ).map(category => ({ + caption: intl.formatMessage(messages.category), + label: category.name, + onClick: () => navigate(categoryUrl(category.id)), + score: score(category.name, search), + text: category.name, + type: "catalog" + })); + + const collections: QuickSearchActionInput[] = maybe( + () => catalog.collections.edges.map(edge => edge.node), + [] + ).map(collection => ({ + caption: intl.formatMessage(messages.collection), + label: collection.name, + onClick: () => navigate(collectionUrl(collection.id)), + score: score(collection.name, search), + text: collection.name, + type: "catalog" + })); + + const products: QuickSearchActionInput[] = maybe( + () => catalog.products.edges.map(edge => edge.node), + [] + ).map(product => ({ + caption: intl.formatMessage(messages.product), + label: product.name, + onClick: () => navigate(productUrl(product.id)), + score: score(product.name, search), + text: product.name, + type: "catalog" + })); + + return [...categories, ...collections, ...products]; +} + +function getCatalogModeActions( + query: string, + intl: IntlShape, + navigate: UseNavigatorResult, + catalog: SearchCatalog +): QuickSearchAction[] { + return searchInCatalog(query, intl, navigate, catalog); +} + +export default getCatalogModeActions; diff --git a/src/components/Navigator/modes/index.ts b/src/components/Navigator/modes/index.ts index c26d4c8bf..6acb95daf 100644 --- a/src/components/Navigator/modes/index.ts +++ b/src/components/Navigator/modes/index.ts @@ -4,6 +4,7 @@ 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"; @@ -21,6 +22,8 @@ function getModeActions( } ): QuickSearchAction[] { switch (mode) { + case "catalog": + return getCatalogModeActions(query, intl, cbs.navigate, queries.catalog); case "commands": return getCommandModeActions(query, intl, cbs.navigate, cbs.createOrder); case "customers": diff --git a/src/components/Navigator/modes/messages.ts b/src/components/Navigator/modes/messages.ts index edeef81b0..df0881147 100644 --- a/src/components/Navigator/modes/messages.ts +++ b/src/components/Navigator/modes/messages.ts @@ -21,6 +21,14 @@ const messages = defineMessages({ defaultMessage: "Add Voucher", description: "button" }, + category: { + defaultMessage: "Category", + description: "catalog item type" + }, + collection: { + defaultMessage: "Collection", + description: "catalog item type" + }, createOrder: { defaultMessage: "Create Order", description: "button" @@ -31,6 +39,10 @@ const messages = defineMessages({ goToOrder: { defaultMessage: "Go to order #{orderNumber}", description: "navigator action" + }, + product: { + defaultMessage: "Product", + description: "catalog item type" } }); diff --git a/src/components/Navigator/modes/types.ts b/src/components/Navigator/modes/types.ts index b207d7e35..13bab413f 100644 --- a/src/components/Navigator/modes/types.ts +++ b/src/components/Navigator/modes/types.ts @@ -1,7 +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; } diff --git a/src/components/Navigator/modes/utils.ts b/src/components/Navigator/modes/utils.ts index c625f0ddd..646647a65 100644 --- a/src/components/Navigator/modes/utils.ts +++ b/src/components/Navigator/modes/utils.ts @@ -22,3 +22,10 @@ export function getCustomers( 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; +} diff --git a/src/components/Navigator/queries/types/SearchCatalog.ts b/src/components/Navigator/queries/types/SearchCatalog.ts new file mode 100644 index 000000000..0fd38ea68 --- /dev/null +++ b/src/components/Navigator/queries/types/SearchCatalog.ts @@ -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; +} diff --git a/src/components/Navigator/queries/useCatalogSearch.ts b/src/components/Navigator/queries/useCatalogSearch.ts new file mode 100644 index 000000000..acf80c47d --- /dev/null +++ b/src/components/Navigator/queries/useCatalogSearch.ts @@ -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 +); + +type UseSearchCatalog = [ + UseQueryResult, + (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; diff --git a/src/components/Navigator/types.ts b/src/components/Navigator/types.ts index 6a9a505e1..1f7421860 100644 --- a/src/components/Navigator/types.ts +++ b/src/components/Navigator/types.ts @@ -1,4 +1,4 @@ -export type QuickSearchActionType = "action" | "customer" | "view"; +export type QuickSearchActionType = "action" | "catalog" | "customer" | "view"; export interface QuickSearchAction { caption?: string; @@ -14,4 +14,9 @@ export interface QuickSearchActionInput extends QuickSearchAction { text: string; } -export type QuickSearchMode = "default" | "commands" | "orders" | "customers"; +export type QuickSearchMode = + | "default" + | "catalog" + | "commands" + | "orders" + | "customers"; diff --git a/src/components/Navigator/useQuickSearch.ts b/src/components/Navigator/useQuickSearch.ts index 316540fbb..63f9bbc6b 100644 --- a/src/components/Navigator/useQuickSearch.ts +++ b/src/components/Navigator/useQuickSearch.ts @@ -11,6 +11,7 @@ import { orderUrl } from "@saleor/orders/urls"; import useCustomerSearch from "@saleor/searches/useCustomerSearch"; import getModeActions from "./modes"; import { getGqlOrderId, isQueryValidOrderNumber } from "./modes/orders"; +import useSearchCatalog from "./queries/useCatalogSearch"; import useCheckIfOrderExists from "./queries/useCheckIfOrderExists"; import { QuickSearchAction, QuickSearchMode } from "./types"; @@ -35,6 +36,7 @@ function useQuickSearch( first: 5 } }); + const [{ data: catalog }, searchCatalog] = useSearchCatalog(5); const [createOrder] = useOrderDraftCreateMutation({ onCompleted: result => { if (result.draftOrderCreate.errors.length === 0) { @@ -86,6 +88,9 @@ function useQuickSearch( case "# ": setMode("orders"); break; + case "$ ": + setMode("catalog"); + break; default: setQuery(value); } @@ -93,6 +98,9 @@ function useQuickSearch( if (mode === "orders" && isQueryValidOrderNumber(value)) { getOrderData(getGqlOrderId(value)); } + if (mode === "catalog") { + searchCatalog(value); + } setQuery(value); } @@ -110,6 +118,7 @@ function useQuickSearch( query, intl, { + catalog, customers: maybe( () => customers.data.search.edges.map(edge => edge.node), [] From 9dda0afb727fc9e604630b5f274d5791b4dd61cc Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 25 Nov 2019 15:57:50 +0100 Subject: [PATCH 12/22] Improve collection search --- src/components/Navigator/modes/catalog.ts | 76 +++++++++++++------ .../Navigator/modes/commands/actions.ts | 3 +- .../Navigator/modes/default/default.ts | 3 +- src/components/Navigator/modes/messages.ts | 8 ++ src/components/Navigator/modes/utils.ts | 9 ++- 5 files changed, 71 insertions(+), 28 deletions(-) diff --git a/src/components/Navigator/modes/catalog.ts b/src/components/Navigator/modes/catalog.ts index da7bd6233..6a90e8a79 100644 --- a/src/components/Navigator/modes/catalog.ts +++ b/src/components/Navigator/modes/catalog.ts @@ -9,6 +9,7 @@ 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 threshold = 0.05; const maxActions = 5; @@ -22,40 +23,65 @@ export function searchInCatalog( const categories: QuickSearchActionInput[] = maybe( () => catalog.categories.edges.map(edge => edge.node), [] - ).map(category => ({ - caption: intl.formatMessage(messages.category), - label: category.name, - onClick: () => navigate(categoryUrl(category.id)), - score: score(category.name, search), - text: category.name, - type: "catalog" - })); + ) + .map(category => ({ + caption: intl.formatMessage(messages.category), + label: category.name, + onClick: () => navigate(categoryUrl(category.id)), + score: score(category.name, search), + text: category.name, + type: "catalog" + })) + .sort(sortScores); const collections: QuickSearchActionInput[] = maybe( () => catalog.collections.edges.map(edge => edge.node), [] - ).map(collection => ({ - caption: intl.formatMessage(messages.collection), - label: collection.name, - onClick: () => navigate(collectionUrl(collection.id)), - score: score(collection.name, search), - text: collection.name, - type: "catalog" - })); + ) + .map(collection => ({ + caption: intl.formatMessage(messages.collection), + extraInfo: intl.formatMessage( + collection.isPublished + ? messages.collectionPublished + : messages.collectionUnpublished + ), + label: collection.name, + onClick: () => navigate(collectionUrl(collection.id)), + score: score(collection.name, search), + text: collection.name, + type: "catalog" + })) + .sort(sortScores); const products: QuickSearchActionInput[] = maybe( () => catalog.products.edges.map(edge => edge.node), [] - ).map(product => ({ - caption: intl.formatMessage(messages.product), - label: product.name, - onClick: () => navigate(productUrl(product.id)), - score: score(product.name, search), - text: product.name, - type: "catalog" - })); + ) + .map(product => ({ + caption: intl.formatMessage(messages.product), + extraInfo: product.category.name, + label: product.name, + onClick: () => navigate(productUrl(product.id)), + score: score(product.name, search), + text: product.name, + type: "catalog" + })) + .sort(sortScores); - return [...categories, ...collections, ...products]; + 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) + ].sort(sortScores); } function getCatalogModeActions( diff --git a/src/components/Navigator/modes/commands/actions.ts b/src/components/Navigator/modes/commands/actions.ts index 4f2bc1d23..fe5e428eb 100644 --- a/src/components/Navigator/modes/commands/actions.ts +++ b/src/components/Navigator/modes/commands/actions.ts @@ -11,6 +11,7 @@ import { productAddUrl } from "@saleor/products/urls"; import { MutationFunction } from "react-apollo"; import { QuickSearchActionInput } from "../../types"; import messages from "../messages"; +import { sortScores } from "../utils"; const threshold = 0.05; const maxActions = 5; @@ -69,7 +70,7 @@ function getCommandModeActions( ): QuickSearchActionInput[] { return [...searchInCommands(query, intl, navigate, createOrder)] .filter(action => action.score >= threshold) - .sort((a, b) => (a.score <= b.score ? 1 : -1)) + .sort(sortScores) .slice(0, maxActions); } diff --git a/src/components/Navigator/modes/default/default.ts b/src/components/Navigator/modes/default/default.ts index 8ed15741a..c54ad2524 100644 --- a/src/components/Navigator/modes/default/default.ts +++ b/src/components/Navigator/modes/default/default.ts @@ -7,6 +7,7 @@ import { SearchCustomers_search_edges_node } from "@saleor/searches/types/Search import { QuickSearchAction } from "../../types"; import { searchInCommands } from "../commands"; import { searchInCustomers } from "../customers"; +import { sortScores } from "../utils"; import searchInViews from "./views"; const threshold = 0.05; @@ -24,7 +25,7 @@ function getDefaultModeActions( ...searchInCommands(query, intl, navigate, createOrder) ] .filter(action => action.score >= threshold) - .sort((a, b) => (a.score <= b.score ? 1 : -1)) + .sort(sortScores) .slice(0, maxActions); if (query !== "") { diff --git a/src/components/Navigator/modes/messages.ts b/src/components/Navigator/modes/messages.ts index df0881147..ef909af71 100644 --- a/src/components/Navigator/modes/messages.ts +++ b/src/components/Navigator/modes/messages.ts @@ -29,6 +29,14 @@ const messages = defineMessages({ defaultMessage: "Collection", description: "catalog item type" }, + collectionPublished: { + defaultMessage: "Published", + description: "collection" + }, + collectionUnpublished: { + defaultMessage: "Not Published", + description: "collection" + }, createOrder: { defaultMessage: "Create Order", description: "button" diff --git a/src/components/Navigator/modes/utils.ts b/src/components/Navigator/modes/utils.ts index 646647a65..17ac2b1ba 100644 --- a/src/components/Navigator/modes/utils.ts +++ b/src/components/Navigator/modes/utils.ts @@ -1,4 +1,4 @@ -import { QuickSearchAction } from "../types"; +import { QuickSearchAction, QuickSearchActionInput } from "../types"; export function getActions(actions: QuickSearchAction[]): QuickSearchAction[] { return actions.filter(action => action.type === "action"); @@ -29,3 +29,10 @@ export function getCatalog(actions: QuickSearchAction[]): QuickSearchAction[] { 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; +} From b069bc885d7315ad639e5b8ddac8134c1a0886e7 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 25 Nov 2019 15:59:11 +0100 Subject: [PATCH 13/22] Fix action number --- src/components/Navigator/modes/catalog.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/components/Navigator/modes/catalog.ts b/src/components/Navigator/modes/catalog.ts index 6a90e8a79..41e018389 100644 --- a/src/components/Navigator/modes/catalog.ts +++ b/src/components/Navigator/modes/catalog.ts @@ -11,7 +11,6 @@ import { QuickSearchAction, QuickSearchActionInput } from "../types"; import messages from "./messages"; import { sortScores } from "./utils"; -const threshold = 0.05; const maxActions = 5; export function searchInCatalog( @@ -76,11 +75,9 @@ export function searchInCatalog( return [ ...baseActions, - ...[ - ...categories.slice(1), - ...collections.slice(1), - ...products.slice(1) - ].sort(sortScores) + ...[...categories.slice(1), ...collections.slice(1), ...products.slice(1)] + .sort(sortScores) + .slice(0, maxActions - baseActions.length) ].sort(sortScores); } From bf0f5b4ef353124670fcf8a0494d1b73a8c201ed Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 25 Nov 2019 16:42:24 +0100 Subject: [PATCH 14/22] Change shortcut --- src/components/Navigator/Navigator.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/Navigator/Navigator.tsx b/src/components/Navigator/Navigator.tsx index 90eb55ff1..2809851fd 100644 --- a/src/components/Navigator/Navigator.tsx +++ b/src/components/Navigator/Navigator.tsx @@ -19,7 +19,7 @@ import NavigatorSection from "./NavigatorSection"; import { QuickSearchAction } from "./types"; import useQuickSearch from "./useQuickSearch"; -const navigatorHotkey = "ctrl+m, command+m"; +const navigatorHotkey = "ctrl+k, command+k"; function getItemOffset( actions: QuickSearchAction[], @@ -35,7 +35,10 @@ const Navigator: React.FC = () => { const intl = useIntl(); React.useEffect(() => { - hotkeys(navigatorHotkey, () => setVisible(true)); + hotkeys(navigatorHotkey, event => { + event.preventDefault(); + setVisible(!visible); + }); return () => hotkeys.unbind(navigatorHotkey); }, []); From 5cb9529b1f5775c5f523c719affca132809e1be4 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 25 Nov 2019 16:51:28 +0100 Subject: [PATCH 15/22] Do not search customers by default --- src/components/Navigator/modes/default/default.ts | 11 +---------- src/components/Navigator/useQuickSearch.ts | 7 +++---- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/components/Navigator/modes/default/default.ts b/src/components/Navigator/modes/default/default.ts index c54ad2524..09557e4ed 100644 --- a/src/components/Navigator/modes/default/default.ts +++ b/src/components/Navigator/modes/default/default.ts @@ -3,10 +3,8 @@ import { IntlShape } from "react-intl"; import { UseNavigatorResult } from "@saleor/hooks/useNavigator"; import { OrderDraftCreate } from "@saleor/orders/types/OrderDraftCreate"; -import { SearchCustomers_search_edges_node } from "@saleor/searches/types/SearchCustomers"; import { QuickSearchAction } from "../../types"; import { searchInCommands } from "../commands"; -import { searchInCustomers } from "../customers"; import { sortScores } from "../utils"; import searchInViews from "./views"; @@ -17,22 +15,15 @@ function getDefaultModeActions( query: string, intl: IntlShape, navigate: UseNavigatorResult, - customers: SearchCustomers_search_edges_node[], createOrder: MutationFunction ): QuickSearchAction[] { - const actions = [ + return [ ...searchInViews(query, intl, navigate), ...searchInCommands(query, intl, navigate, createOrder) ] .filter(action => action.score >= threshold) .sort(sortScores) .slice(0, maxActions); - - if (query !== "") { - return [...actions, ...searchInCustomers(intl, navigate, customers)]; - } - - return actions; } export default getDefaultModeActions; diff --git a/src/components/Navigator/useQuickSearch.ts b/src/components/Navigator/useQuickSearch.ts index 63f9bbc6b..0eac8ce8c 100644 --- a/src/components/Navigator/useQuickSearch.ts +++ b/src/components/Navigator/useQuickSearch.ts @@ -101,12 +101,11 @@ function useQuickSearch( if (mode === "catalog") { searchCatalog(value); } + if (mode === "customers") { + searchCustomers(value); + } setQuery(value); } - - if ((["customers", "default"] as QuickSearchMode[]).includes(mode)) { - searchCustomers(value); - } }; return [ From 54feb609508b2da56851a7f795cf29b51f97eba4 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 25 Nov 2019 17:23:52 +0100 Subject: [PATCH 16/22] Add notification about navigator --- package-lock.json | 8 +++++-- package.json | 2 ++ src/components/Navigator/Navigator.tsx | 25 ++++++++++++++++++++++ src/components/Navigator/modes/index.ts | 8 +------ src/components/messages/MessageManager.tsx | 11 ++++++++-- src/components/messages/index.ts | 1 + src/config.ts | 3 +++ src/theme.ts | 8 +++++-- 8 files changed, 53 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3af4cf4fd..41c04c99a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", @@ -18055,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", diff --git a/package.json b/package.json index 52c21d332..74788cf4c 100644 --- a/package.json +++ b/package.json @@ -63,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", @@ -98,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", diff --git a/src/components/Navigator/Navigator.tsx b/src/components/Navigator/Navigator.tsx index 2809851fd..3616b244c 100644 --- a/src/components/Navigator/Navigator.tsx +++ b/src/components/Navigator/Navigator.tsx @@ -3,7 +3,11 @@ 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, @@ -20,6 +24,7 @@ import { QuickSearchAction } from "./types"; import useQuickSearch from "./useQuickSearch"; const navigatorHotkey = "ctrl+k, command+k"; +const navigatorNotificationStorageKey = "notifiedAboutNavigator"; function getItemOffset( actions: QuickSearchAction[], @@ -33,6 +38,11 @@ const Navigator: React.FC = () => { 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 + ); React.useEffect(() => { hotkeys(navigatorHotkey, event => { @@ -40,6 +50,21 @@ const Navigator: React.FC = () => { setVisible(!visible); }); + if (cmp(APP_VERSION, "2.1.0") !== 1 && !notifiedAboutNavigator) { + notify({ + text: intl.formatMessage({ + defaultMessage: + "Our new feature to help you with your daily task. Run Navigator using Ctrl+K shortcut. (Cmd+K for Mac users)", + description: "navigator notification" + }), + title: intl.formatMessage({ + defaultMessage: "Navigator is here to help", + description: "navigator notification title" + }) + }); + setNotifiedAboutNavigator(true); + } + return () => hotkeys.unbind(navigatorHotkey); }, []); diff --git a/src/components/Navigator/modes/index.ts b/src/components/Navigator/modes/index.ts index 6acb95daf..4333883e9 100644 --- a/src/components/Navigator/modes/index.ts +++ b/src/components/Navigator/modes/index.ts @@ -31,13 +31,7 @@ function getModeActions( case "orders": return getOrdersModeActions(query, intl, cbs.navigate, queries.order); default: - return getDefaultModeActions( - query, - intl, - cbs.navigate, - queries.customers, - cbs.createOrder - ); + return getDefaultModeActions(query, intl, cbs.navigate, cbs.createOrder); } } diff --git a/src/components/messages/MessageManager.tsx b/src/components/messages/MessageManager.tsx index 11f4f659f..2ab9a38f4 100644 --- a/src/components/messages/MessageManager.tsx +++ b/src/components/messages/MessageManager.tsx @@ -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 { title, text, key, onUndo } = this.state.message; return ( <> { }} message={ + {title && ( + + {title} + + )} {text} } + title={title} action={[ !!onUndo ? ( diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 9c231db02..ec9718967 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -29700,7 +29700,7 @@ exports[`Storyshots Views / Customers / Create customer default 1`] = `
- Add Customer + Create Customer
- Add Customer + Create Customer
- Add Customer + Create Customer
- Add customer + Create customer
@@ -37802,7 +37802,7 @@ exports[`Storyshots Views / Customers / Customer list loading 1`] = ` - Add customer + Create customer
@@ -38131,7 +38131,7 @@ exports[`Storyshots Views / Customers / Customer list no data 1`] = ` - Add customer + Create customer