diff --git a/.changeset/thick-kiwis-study.md b/.changeset/thick-kiwis-study.md new file mode 100644 index 00000000000..0aba0d06900 --- /dev/null +++ b/.changeset/thick-kiwis-study.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": patch +--- + +When navigating to order details from the order list, the back button will now return you to the previous page with the same filters and pagination applied. diff --git a/src/components/Datagrid/Datagrid.tsx b/src/components/Datagrid/Datagrid.tsx index 8a530a27d35..5623cd1391e 100644 --- a/src/components/Datagrid/Datagrid.tsx +++ b/src/components/Datagrid/Datagrid.tsx @@ -1,6 +1,6 @@ import "@glideapps/glide-data-grid/dist/index.css"; -import useNavigator from "@dashboard/hooks/useNavigator"; +import useNavigator, { NavigatorOpts } from "@dashboard/hooks/useNavigator"; import { usePreventHistoryBack } from "@dashboard/hooks/usePreventHistoryBack"; import { getCellAction } from "@dashboard/products/components/ProductListDatagrid/datagrid"; import DataEditor, { @@ -98,6 +98,7 @@ export interface DatagridProps { recentlyAddedColumn?: string | null; // Enables scroll to recently added column onClearRecentlyAddedColumn?: () => void; renderHeader?: (props: DatagridRenderHeaderProps) => ReactNode; + navigatorOpts?: NavigatorOpts; } export const Datagrid: React.FC = ({ @@ -130,6 +131,7 @@ export const Datagrid: React.FC = ({ onClearRecentlyAddedColumn, rowHeight = cellHeight, renderHeader, + navigatorOpts, ...datagridProps }): ReactElement => { const classes = useStyles({ actionButtonPosition }); @@ -531,7 +533,7 @@ export const Datagrid: React.FC = ({ e.preventDefault(); if (e.currentTarget.dataset.reactRouterPath) { - navigate(e.currentTarget.dataset.reactRouterPath); + navigate(e.currentTarget.dataset.reactRouterPath, navigatorOpts); } }} /> diff --git a/src/hooks/useBackLinkWithState.test.ts b/src/hooks/useBackLinkWithState.test.ts new file mode 100644 index 00000000000..dc8f0c442bf --- /dev/null +++ b/src/hooks/useBackLinkWithState.test.ts @@ -0,0 +1,72 @@ +import { renderHook } from "@testing-library/react-hooks"; +import { useLocation } from "react-router"; + +import { useBackLinkWithState } from "./useBackLinkWithState"; + +jest.mock("react-router", () => ({ + useLocation: jest.fn(), +})); + +describe("useBackLinkWithState", () => { + // Arrange + it("should return path if there is no previous location in state", () => { + (useLocation as jest.Mock).mockReturnValue({ + state: {}, + }); + + // Act + const { result } = renderHook(() => + useBackLinkWithState({ + path: "/orders", + }), + ); + + // Assert + expect(result.current).toBe("/orders"); + }); + + it("should return the previous URL if it is an order list path", () => { + // Arrange + + (useLocation as jest.Mock).mockReturnValue({ + state: { + prevLocation: { + pathname: "/orders", + search: "?asc=false&after=cursor", + }, + }, + }); + + // Act + const { result } = renderHook(() => + useBackLinkWithState({ + path: "/orders", + }), + ); + + // Assert + expect(result.current).toBe("/orders?asc=false&after=cursor"); + }); + + it("should return the previous URL if it is a draft order list path", () => { + // Arrange + (useLocation as jest.Mock).mockReturnValue({ + state: { + prevLocation: { + pathname: "/orders/drafts", + search: "?asc=false&after=cursor", + }, + }, + }); + + // Act + const { result } = renderHook(() => + useBackLinkWithState({ + path: "/orders/drafts", + }), + ); + + // Assert + expect(result.current).toBe("/orders/drafts?asc=false&after=cursor"); + }); +}); diff --git a/src/hooks/useBackLinkWithState.ts b/src/hooks/useBackLinkWithState.ts new file mode 100644 index 00000000000..0ab46203920 --- /dev/null +++ b/src/hooks/useBackLinkWithState.ts @@ -0,0 +1,43 @@ +import { useEffect, useState } from "react"; +import { useLocation } from "react-router"; +import urljoin from "url-join"; + +type LocationWithState = Location & { + state?: { + prevLocation?: Location; + }; +}; + +const getPreviousUrl = (location: LocationWithState) => { + if (!location.state?.prevLocation) { + return null; + } + + const { pathname, search } = location.state.prevLocation; + + return urljoin(pathname, search); +}; + +interface UseBackLinkWithState { + path: string; +} + +export const useBackLinkWithState = ({ path }: UseBackLinkWithState) => { + const location = useLocation(); + const [backLink, setBackLink] = useState(path); + + useEffect(() => { + if (location.state) { + const previousUrl = getPreviousUrl(location as LocationWithState); + + // Prevent other links from being set as back link + const isCorrectPath = previousUrl?.includes(path); + + if (isCorrectPath && previousUrl) { + setBackLink(previousUrl); + } + } + }, [location, path]); + + return backLink; +}; diff --git a/src/hooks/useNavigator.ts b/src/hooks/useNavigator.ts index d507ee8fc59..268bdad8896 100644 --- a/src/hooks/useNavigator.ts +++ b/src/hooks/useNavigator.ts @@ -2,14 +2,14 @@ import { ExitFormDialogContext } from "@dashboard/components/Form/ExitFormDialog import { useContext } from "react"; import useRouter from "use-react-router"; -export type UseNavigatorResult = ( - url: string, - opts?: { - replace?: boolean; - preserveQs?: boolean; - resetScroll?: boolean; - }, -) => void; +export type NavigatorOpts = { + replace?: boolean; + preserveQs?: boolean; + resetScroll?: boolean; + state?: Record; +}; + +export type UseNavigatorResult = (url: string, opts?: NavigatorOpts) => void; function useNavigator(): UseNavigatorResult { const { location: { search }, @@ -17,7 +17,10 @@ function useNavigator(): UseNavigatorResult { } = useRouter(); const { shouldBlockNavigation } = useContext(ExitFormDialogContext); - return (url: string, { replace = false, preserveQs = false, resetScroll = false } = {}) => { + return ( + url: string, + { replace = false, preserveQs = false, resetScroll = false, state } = {}, + ) => { if (shouldBlockNavigation()) { return; } @@ -25,9 +28,9 @@ function useNavigator(): UseNavigatorResult { const targetUrl = preserveQs ? url + search : url; if (replace) { - history.replace(targetUrl); + history.replace(targetUrl, state); } else { - history.push(targetUrl); + history.push(targetUrl, state); } if (resetScroll) { diff --git a/src/orders/components/OrderDetailsPage/OrderDetailsPage.tsx b/src/orders/components/OrderDetailsPage/OrderDetailsPage.tsx index 9796985be46..f138cf2640d 100644 --- a/src/orders/components/OrderDetailsPage/OrderDetailsPage.tsx +++ b/src/orders/components/OrderDetailsPage/OrderDetailsPage.tsx @@ -20,6 +20,7 @@ import { OrderStatus, TransactionActionEnum, } from "@dashboard/graphql"; +import { useBackLinkWithState } from "@dashboard/hooks/useBackLinkWithState"; import { SubmitPromise } from "@dashboard/hooks/useForm"; import useNavigator from "@dashboard/hooks/useNavigator"; import { defaultGraphiQLQuery } from "@dashboard/orders/queries"; @@ -172,6 +173,10 @@ const OrderDetailsPage: React.FC = props => { context.setDevModeVisibility(true); }; + const backLinkUrl = useBackLinkWithState({ + path: orderListUrl(), + }); + return (
{({ set, triggerChange, data, submit }) => { @@ -179,7 +184,7 @@ const OrderDetailsPage: React.FC = props => { return ( - }> + }> { + const location = useLocation(); const intl = useIntl(); const { locale } = useLocale(); const datagridState = useDatagridChangeState(); @@ -123,6 +125,11 @@ export const OrderDraftListDatagrid = ({ onToggle={handlers.onToggle} /> )} + navigatorOpts={{ + state: { + prevLocation: location, + }, + }} /> diff --git a/src/orders/components/OrderDraftPage/OrderDraftPage.tsx b/src/orders/components/OrderDraftPage/OrderDraftPage.tsx index af7d436f5bd..83512b4a8f7 100644 --- a/src/orders/components/OrderDraftPage/OrderDraftPage.tsx +++ b/src/orders/components/OrderDraftPage/OrderDraftPage.tsx @@ -13,6 +13,7 @@ import { OrderLineInput, SearchCustomersQuery, } from "@dashboard/graphql"; +import { useBackLinkWithState } from "@dashboard/hooks/useBackLinkWithState"; import { SubmitPromise } from "@dashboard/hooks/useForm"; import useNavigator from "@dashboard/hooks/useNavigator"; import OrderChannelSectionCard from "@dashboard/orders/components/OrderChannelSectionCard"; @@ -51,6 +52,8 @@ export interface OrderDraftPageProps extends FetchMoreProps { onShowMetadata: (id: string) => void; } +const draftOrderListUrl = orderDraftListUrl(); + const OrderDraftPage: React.FC = props => { const { loading, @@ -78,11 +81,14 @@ const OrderDraftPage: React.FC = props => { } = props; const navigate = useNavigator(); const intl = useIntl(); + const backLinkUrl = useBackLinkWithState({ + path: draftOrderListUrl, + }); return ( {order?.number ? "#" + order?.number : undefined} diff --git a/src/orders/components/OrderListDatagrid/OrderListDatagrid.tsx b/src/orders/components/OrderListDatagrid/OrderListDatagrid.tsx index 5b42223a186..02638eb522e 100644 --- a/src/orders/components/OrderListDatagrid/OrderListDatagrid.tsx +++ b/src/orders/components/OrderListDatagrid/OrderListDatagrid.tsx @@ -15,6 +15,7 @@ import { Item } from "@glideapps/glide-data-grid"; import { Box } from "@saleor/macaw-ui-next"; import React, { useCallback, useMemo } from "react"; import { useIntl } from "react-intl"; +import { useLocation } from "react-router"; import { orderListStaticColumnAdapter, useGetCellContent } from "./datagrid"; import { messages } from "./messages"; @@ -38,6 +39,7 @@ export const OrderListDatagrid: React.FC = ({ hasRowHover, rowAnchor, }) => { + const location = useLocation(); const intl = useIntl(); const datagrid = useDatagridChangeState(); const ordersLength = getOrdersRowsLength(orders, disabled); @@ -127,6 +129,11 @@ export const OrderListDatagrid: React.FC = ({ )} onRowClick={handleRowClick} rowAnchor={handleRowAnchor} + navigatorOpts={{ + state: { + prevLocation: location, + }, + }} />