diff --git a/src/app/App.js b/src/app/App.js
index b117efe..72c802f 100644
--- a/src/app/App.js
+++ b/src/app/App.js
@@ -1,7 +1,7 @@
import React from "react";
import { ApolloProvider } from "@apollo/client";
import { Auth0Provider } from "@auth0/auth0-react";
-import { CSSReset, ChakraProvider, extendTheme } from "@chakra-ui/react";
+import { CSSReset, ChakraProvider, extendTheme, Box } from "@chakra-ui/react";
import { mode } from "@chakra-ui/theme-tools";
import {
BrowserRouter as Router,
@@ -45,6 +45,17 @@ const WardrobePage = loadable(() => import("./WardrobePage"), {
fallback: ,
});
+// ItemPage and ItemSearchPage need to share a search toolbar, so here it is!
+// It'll load in dynamically like the page elements, with a hacky fallback to
+// take up 40px of height until it loads.
+//
+// There very well be a better way to encapsulate this! It's not *great* to
+// have this here. I just don't wanna over abstract it just yet 😅
+const ItemSearchPageToolbar = loadable(
+ () => import("./components/ItemSearchPageToolbar"),
+ { fallback: }
+);
+
const theme = extendTheme({
styles: {
global: (props) => ({
@@ -116,6 +127,7 @@ function App() {
+
@@ -131,6 +143,7 @@ function App() {
+
diff --git a/src/app/ItemPage.js b/src/app/ItemPage.js
index 0cb0006..dc69b91 100644
--- a/src/app/ItemPage.js
+++ b/src/app/ItemPage.js
@@ -31,7 +31,7 @@ import { useQuery, useMutation } from "@apollo/client";
import { Link, useParams } from "react-router-dom";
import ItemPageLayout, { SubtleSkeleton } from "./ItemPageLayout";
-import { Delay, logAndCapture, usePageTitle } from "./util";
+import { Delay, logAndCapture, useLocalStorage, usePageTitle } from "./util";
import HTML5Badge, { layerUsesHTML5 } from "./components/HTML5Badge";
import {
itemAppearanceFragment,
@@ -44,7 +44,6 @@ import SpeciesColorPicker, {
getClosestPose,
} from "./components/SpeciesColorPicker";
import useCurrentUser from "./components/useCurrentUser";
-import { useLocalStorage } from "./util";
import SpeciesFacesPicker, {
colorIsBasic,
} from "./ItemPage/SpeciesFacesPicker";
diff --git a/src/app/ItemSearchPage.js b/src/app/ItemSearchPage.js
index 38ca1f9..8378067 100644
--- a/src/app/ItemSearchPage.js
+++ b/src/app/ItemSearchPage.js
@@ -1,98 +1,21 @@
-import React from "react";
import { Box, Wrap, WrapItem } from "@chakra-ui/react";
import gql from "graphql-tag";
import { useQuery } from "@apollo/client";
-import { useHistory, useLocation, useParams } from "react-router-dom";
-import SearchToolbar, {
+import {
emptySearchQuery,
searchQueryIsEmpty,
} from "./WardrobePage/SearchToolbar";
import SquareItemCard, {
SquareItemCardSkeleton,
} from "./components/SquareItemCard";
-import { Delay, MajorErrorMessage, useCommonStyles, useDebounce } from "./util";
+import { Delay, MajorErrorMessage, useDebounce } from "./util";
import PaginationToolbar from "./components/PaginationToolbar";
+import { useSearchQueryInUrl } from "./components/ItemSearchPageToolbar";
function ItemSearchPage() {
- const [query, offset, setQuery] = useSearchQueryInUrl();
- const { brightBackground } = useCommonStyles();
+ const { query: latestQuery, offset } = useSearchQueryInUrl();
- return (
-
-
-
-
-
- );
-}
-
-/**
- * useSearchQueryInUrl provides an API like useState, but stores the search
- * query in the URL! It also parses out the offset for us.
- */
-function useSearchQueryInUrl() {
- const history = useHistory();
-
- const { query: value } = useParams();
- const { search } = useLocation();
- const searchParams = new URLSearchParams(search);
-
- const query = {
- value: decodeURIComponent(value || ""),
- filterToZoneLabel: searchParams.get("zone") || null,
- filterToItemKind: searchParams.get("kind") || null,
- filterToCurrentUserOwnsOrWants: searchParams.get("user") || null,
- };
-
- const offset = parseInt(searchParams.get("offset")) || 0;
-
- const setQuery = React.useCallback(
- (newQuery) => {
- let url = `/items/search`;
-
- if (newQuery.value) {
- url += "/" + encodeURIComponent(newQuery.value);
- }
-
- const newParams = new URLSearchParams();
- if (newQuery.filterToItemKind) {
- newParams.append("kind", newQuery.filterToItemKind);
- }
- if (newQuery.filterToZoneLabel) {
- newParams.append("zone", newQuery.filterToZoneLabel);
- }
- if (newQuery.filterToCurrentUserOwnsOrWants) {
- newParams.append("user", newQuery.filterToCurrentUserOwnsOrWants);
- }
-
- // NOTE: We omit `offset`, because changing the query should reset us
- // back to the first page!
-
- const search = newParams.toString();
- if (search) {
- url += "?" + search;
- }
-
- history.replace(url);
- },
- [history]
- );
-
- // NOTE: We don't provide a `setOffset`, because that's handled via
- // pagination links.
-
- return [query, offset, setQuery];
-}
-
-function ItemSearchPageResults({ query: latestQuery, offset }) {
// NOTE: Some of this is copied from SearchPanel... but all of this is messy
// enough that I'm not comfy code-sharing yet, esp since I feel like
// SearchPanel pagination is a bit of a mess and will need refactoring.
diff --git a/src/app/components/ItemSearchPageToolbar.js b/src/app/components/ItemSearchPageToolbar.js
new file mode 100644
index 0000000..f3ea319
--- /dev/null
+++ b/src/app/components/ItemSearchPageToolbar.js
@@ -0,0 +1,106 @@
+import React from "react";
+import { useHistory, useLocation, useParams } from "react-router-dom";
+import { useCommonStyles } from "../util";
+import SearchToolbar from "../WardrobePage/SearchToolbar";
+
+function ItemSearchPageToolbar({ ...props }) {
+ const { query, setQuery } = useSearchQueryInUrl();
+ const { brightBackground } = useCommonStyles();
+
+ return (
+
+ );
+}
+
+/**
+ * useSearchQueryInUrl provides an API like useState, but stores the search
+ * query in the URL! It also parses out the offset for us.
+ */
+export function useSearchQueryInUrl() {
+ const history = useHistory();
+
+ const { query: value } = useParams();
+ const { pathname, search } = useLocation();
+
+ // Parse the query from the location. (We memoize this because we use it as a
+ // dependency in the query-saving hook below.)
+ const parsedQuery = React.useMemo(() => {
+ const searchParams = new URLSearchParams(search);
+ return {
+ value: decodeURIComponent(value || ""),
+ filterToZoneLabel: searchParams.get("zone") || null,
+ filterToItemKind: searchParams.get("kind") || null,
+ filterToCurrentUserOwnsOrWants: searchParams.get("user") || null,
+ };
+ }, [search, value]);
+
+ const offset = parseInt(new URLSearchParams(search).get("offset")) || 0;
+
+ // While on the search page, save the most recent parsed query in state.
+ const isSearchPage = pathname.startsWith("/items/search");
+ const [savedQuery, setSavedQuery] = React.useState(parsedQuery);
+ React.useEffect(() => {
+ if (isSearchPage) {
+ setSavedQuery(parsedQuery);
+ }
+ }, [isSearchPage, parsedQuery]);
+
+ // Then, while not on the search page, use the saved query from state,
+ // instead of the (presumably empty) parsed query from the URL.
+ const query = isSearchPage ? parsedQuery : savedQuery;
+
+ const setQuery = React.useCallback(
+ (newQuery) => {
+ let url = `/items/search`;
+
+ if (newQuery.value) {
+ url += "/" + encodeURIComponent(newQuery.value);
+ }
+
+ const newParams = new URLSearchParams();
+ if (newQuery.filterToItemKind) {
+ newParams.append("kind", newQuery.filterToItemKind);
+ }
+ if (newQuery.filterToZoneLabel) {
+ newParams.append("zone", newQuery.filterToZoneLabel);
+ }
+ if (newQuery.filterToCurrentUserOwnsOrWants) {
+ newParams.append("user", newQuery.filterToCurrentUserOwnsOrWants);
+ }
+
+ // NOTE: We omit `offset`, because changing the query should reset us
+ // back to the first page!
+ const search = newParams.toString();
+ if (search) {
+ url += "?" + search;
+ }
+
+ // TODO: Tbh would be even nicer for this to be a like... timed thing?
+ // We use replace to avoid spamming the history too much, but sometimes
+ // the user's query meaningfully *does* change without intermediate
+ // navigation, like if they see the results and decide it's the wrong
+ // thing.
+ if (isSearchPage) {
+ history.replace(url);
+ } else {
+ // When you use the search toolbar from the item page, treat it as a
+ // full navigation!
+ history.push(url);
+ }
+ },
+ [history, isSearchPage]
+ );
+
+ // NOTE: We don't provide a `setOffset`, because that's handled via
+ // pagination links.
+ return { query, offset, setQuery };
+}
+
+export default ItemSearchPageToolbar;