1
0
Fork 0
forked from OpenNeo/impress

Remove next/router references

Once again, not really tested, but we don't have the same errors as before so!
This commit is contained in:
Emi Matchu 2023-08-10 17:15:07 -07:00
parent 6a59fa9f02
commit 3c1fcca986
10 changed files with 44 additions and 134 deletions

View file

@ -56,13 +56,13 @@ import useCurrentUser from "./components/useCurrentUser";
import SpeciesFacesPicker, { import SpeciesFacesPicker, {
colorIsBasic, colorIsBasic,
} from "./ItemPage/SpeciesFacesPicker"; } from "./ItemPage/SpeciesFacesPicker";
import { useRouter } from "next/router";
import Head from "next/head";
function ItemPage() { // Removed for the wardrobe-2020 case.
const { query } = useRouter(); // TODO: Refactor this stuff, do we even need ItemPageContent really?
return <ItemPageContent itemId={query.itemId} />; // function ItemPage() {
} // const { query } = useRouter();
// return <ItemPageContent itemId={query.itemId} />;
// }
/** /**
* ItemPageContent is the content of ItemPage, but we also use it as the * ItemPageContent is the content of ItemPage, but we also use it as the
@ -102,11 +102,6 @@ export function ItemPageContent({ itemId, isEmbedded = false }) {
return ( return (
<> <>
{!isEmbedded && item?.name && (
<Head>
<title>{item?.name} | Dress to Impress</title>
</Head>
)}
<ItemPageLayout item={item} isEmbedded={isEmbedded}> <ItemPageLayout item={item} isEmbedded={isEmbedded}>
<VStack spacing="8" marginTop="4"> <VStack spacing="8" marginTop="4">
<ItemPageDescription <ItemPageDescription

View file

@ -403,7 +403,7 @@ function CrossFadeImage(incomingImageProps) {
opacity: 0; opacity: 0;
`} `}
> >
{/* eslint-disable-next-line jsx-a11y/alt-text, @next/next/no-img-element */} {/* eslint-disable-next-line jsx-a11y/alt-text */}
<img {...prevImageProps} aria-hidden /> <img {...prevImageProps} aria-hidden />
</div> </div>
)} )}
@ -416,7 +416,7 @@ function CrossFadeImage(incomingImageProps) {
opacity: 1; opacity: 1;
`} `}
> >
{/* eslint-disable-next-line jsx-a11y/alt-text, @next/next/no-img-element */} {/* eslint-disable-next-line jsx-a11y/alt-text */}
<img <img
{...currentImageProps} {...currentImageProps}
// If the current image _is_ the incoming image, we'll allow // If the current image _is_ the incoming image, we'll allow
@ -438,7 +438,7 @@ function CrossFadeImage(incomingImageProps) {
opacity: 0; opacity: 0;
`} `}
> >
{/* eslint-disable-next-line jsx-a11y/alt-text, @next/next/no-img-element */} {/* eslint-disable-next-line jsx-a11y/alt-text */}
<img <img
{...incomingImageProps} {...incomingImageProps}
aria-hidden aria-hidden

View file

@ -49,7 +49,6 @@ import { IoCloudUploadOutline } from "react-icons/io5";
import { MdMoreVert } from "react-icons/md"; import { MdMoreVert } from "react-icons/md";
import { buildOutfitUrl } from "./useOutfitState"; import { buildOutfitUrl } from "./useOutfitState";
import { gql, useMutation } from "@apollo/client"; import { gql, useMutation } from "@apollo/client";
import { useRouter } from "next/router";
/** /**
* ItemsPanel shows the items in the current outfit, and lets the user toggle * ItemsPanel shows the items in the current outfit, and lets the user toggle
@ -455,7 +454,6 @@ function OutfitHeading({ outfitState, outfitSaving, dispatchToOutfit }) {
function DeleteOutfitMenuItem({ outfitState }) { function DeleteOutfitMenuItem({ outfitState }) {
const { id, name } = outfitState; const { id, name } = outfitState;
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const { push: pushHistory } = useRouter();
const [sendDeleteOutfitMutation, { loading, error }] = useMutation( const [sendDeleteOutfitMutation, { loading, error }] = useMutation(
gql` gql`
@ -506,7 +504,7 @@ function DeleteOutfitMenuItem({ outfitState }) {
onClick={() => onClick={() =>
sendDeleteOutfitMutation({ variables: { id } }) sendDeleteOutfitMutation({ variables: { id } })
.then(() => { .then(() => {
pushHistory(`/your-outfits`); window.location = "/your-outfits";
}) })
.catch((e) => { .catch((e) => {
/* handled in error UI */ /* handled in error UI */

View file

@ -1,6 +1,5 @@
import React from "react"; import React from "react";
import { useToast } from "@chakra-ui/react"; import { useToast } from "@chakra-ui/react";
import { useRouter } from "next/router";
import { emptySearchQuery } from "./SearchToolbar"; import { emptySearchQuery } from "./SearchToolbar";
import ItemsAndSearchPanels from "./ItemsAndSearchPanels"; import ItemsAndSearchPanels from "./ItemsAndSearchPanels";
@ -30,8 +29,9 @@ function WardrobePage() {
// We manage outfit saving up here, rather than at the point of the UI where // We manage outfit saving up here, rather than at the point of the UI where
// "Saving" indicators appear. That way, auto-saving still happens even when // "Saving" indicators appear. That way, auto-saving still happens even when
// the indicator isn't on the page, e.g. when searching. We also mount a // the indicator isn't on the page, e.g. when searching.
// <Prompt /> in this component to prevent navigating away before saving. // NOTE: This only applies to navigations leaving the wardrobe-2020 app, not
// within!
const outfitSaving = useOutfitSaving(outfitState, dispatchToOutfit); const outfitSaving = useOutfitSaving(outfitState, dispatchToOutfit);
// TODO: I haven't found a great place for this error UI yet, and this case // TODO: I haven't found a great place for this error UI yet, and this case
@ -86,20 +86,6 @@ function WardrobePage() {
<WardrobeDevHacks /> <WardrobeDevHacks />
</SupportOnly> </SupportOnly>
{/*
* TODO: This might unnecessarily block navigations that we don't
* necessarily need to, e.g., navigating back to Your Outfits while the
* save request is in flight. We could instead submit the save mutation
* immediately on client-side nav, and have each outfit save mutation
* install a `beforeunload` handler that ensures that you don't close
* the page altogether while it's in flight. But let's start simple and
* see how annoying it actually is in practice lol
*/}
<Prompt
when={shouldBlockNavigation}
message="Are you sure you want to leave? Your changes might not be saved."
/>
<WardrobePageLayout <WardrobePageLayout
previewAndControls={ previewAndControls={
<WardrobePreviewAndControls <WardrobePreviewAndControls
@ -163,36 +149,4 @@ function SavedOutfitMetaTags({ outfitState }) {
); );
} }
/**
* Prompt blocks client-side navigation via Next.js when the `when` prop is
* true. This is our attempt at a drop-in replacement for the Prompt component
* offered by react-router!
*
* Adapted from https://github.com/vercel/next.js/issues/2694#issuecomment-778225625
*/
function Prompt({ when, message }) {
const router = useRouter();
React.useEffect(() => {
const handleWindowClose = (e) => {
if (!when) return;
e.preventDefault();
return (e.returnValue = message);
};
const handleBrowseAway = () => {
if (!when) return;
if (window.confirm(message)) return;
router.events.emit("routeChangeError");
throw "routeChange aborted by <Prompt>.";
};
window.addEventListener("beforeunload", handleWindowClose);
router.events.on("routeChangeStart", handleBrowseAway);
return () => {
window.removeEventListener("beforeunload", handleWindowClose);
router.events.off("routeChangeStart", handleBrowseAway);
};
}, [when, message, router]);
return null;
}
export default WardrobePage; export default WardrobePage;

View file

@ -292,7 +292,6 @@ function AppearanceLayerSupportReviewStep({
marginTop="2" marginTop="2"
> >
{imageWithAlphaUrl && ( {imageWithAlphaUrl && (
// eslint-disable-next-line @next/next/no-img-element
<img <img
src={imageWithAlphaUrl} src={imageWithAlphaUrl}
width={600} width={600}
@ -445,10 +444,8 @@ async function mergeIntoImageWithAlpha(
imageOnWhite, imageOnWhite,
conflictMode conflictMode
); );
const [ const [imageWithAlphaUrl, imageWithAlphaBlob] =
imageWithAlphaUrl, await writeImageDataToUrlAndBlob(imageWithAlphaData);
imageWithAlphaBlob,
] = await writeImageDataToUrlAndBlob(imageWithAlphaData);
return [imageWithAlphaUrl, imageWithAlphaBlob, numWarnings]; return [imageWithAlphaUrl, imageWithAlphaBlob, numWarnings];
} }

View file

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { useToast } from "@chakra-ui/react"; import { useToast } from "@chakra-ui/react";
import { useRouter } from "next/router"; import { useLocation, useNavigate } from "react-router-dom";
import { useDebounce } from "../util"; import { useDebounce } from "../util";
import useCurrentUser from "../components/useCurrentUser"; import useCurrentUser from "../components/useCurrentUser";
import gql from "graphql-tag"; import gql from "graphql-tag";
@ -9,7 +9,8 @@ import { outfitStatesAreEqual } from "./useOutfitState";
function useOutfitSaving(outfitState, dispatchToOutfit) { function useOutfitSaving(outfitState, dispatchToOutfit) {
const { isLoggedIn, id: currentUserId } = useCurrentUser(); const { isLoggedIn, id: currentUserId } = useCurrentUser();
const { pathname, push: pushHistory } = useRouter(); const { pathname } = useLocation();
const navigate = useNavigate();
const toast = useToast(); const toast = useToast();
// There's not a way to reset an Apollo mutation state to clear out the error // There's not a way to reset an Apollo mutation state to clear out the error
@ -159,7 +160,7 @@ function useOutfitSaving(outfitState, dispatchToOutfit) {
// up the data from this mutation response, and combine it with the // up the data from this mutation response, and combine it with the
// existing cached data, to make this smooth without any loading UI. // existing cached data, to make this smooth without any loading UI.
if (pathname !== `/outfits/[outfitId]`) { if (pathname !== `/outfits/[outfitId]`) {
pushHistory(`/outfits/${outfit.id}`); navigate(`/outfits/${outfit.id}`);
} }
}) })
.catch((e) => { .catch((e) => {
@ -175,7 +176,7 @@ function useOutfitSaving(outfitState, dispatchToOutfit) {
// It's important that this callback _doesn't_ change when the outfit // It's important that this callback _doesn't_ change when the outfit
// changes, so that the auto-save effect is only responding to the // changes, so that the auto-save effect is only responding to the
// debounced state! // debounced state!
[sendSaveOutfitMutation, pathname, pushHistory, toast] [sendSaveOutfitMutation, pathname, navigate, toast]
); );
const saveOutfit = React.useCallback( const saveOutfit = React.useCallback(

View file

@ -4,7 +4,6 @@ import produce, { enableMapSet } from "immer";
import { useQuery, useApolloClient } from "@apollo/client"; import { useQuery, useApolloClient } from "@apollo/client";
import { itemAppearanceFragment } from "../components/useOutfitAppearance"; import { itemAppearanceFragment } from "../components/useOutfitAppearance";
import { useRouter } from "next/router";
enableMapSet(); enableMapSet();
@ -382,25 +381,26 @@ const EMPTY_CUSTOMIZATION_STATE = {
}; };
function useParseOutfitUrl() { function useParseOutfitUrl() {
const { query } = useRouter(); const [searchParams] = useSearchParams();
// We memoize this to make `outfitStateWithoutExtras` an even more reliable // We memoize this to make `outfitStateWithoutExtras` an even more reliable
// stable object! // stable object!
const memoizedOutfitState = React.useMemo( const memoizedOutfitState = React.useMemo(
() => readOutfitStateFromQuery(query), () => readOutfitStateFromSearchParams(searchParams),
[query] [query]
); );
return memoizedOutfitState; return memoizedOutfitState;
} }
export function readOutfitStateFromQuery(query) { function readOutfitStateFromSearchParams(searchParams) {
// For the /outfits/:id page, ignore the query string, and just wait for the // For the /outfits/:id page, ignore the query string, and just wait for the
// outfit data to load in! // outfit data to load in!
if (query.outfitId != null) { const outfitId = searchParams.get("outfitId");
if (outfitId != null) {
return { return {
...EMPTY_CUSTOMIZATION_STATE, ...EMPTY_CUSTOMIZATION_STATE,
id: query.outfitId, id: outfitId,
}; };
} }
@ -408,52 +408,16 @@ export function readOutfitStateFromQuery(query) {
// not specified. // not specified.
return { return {
id: null, id: null,
name: getValueFromQuery(query.name), name: searchParams.get("name"),
speciesId: getValueFromQuery(query.species) || "1", speciesId: searchParams.get("species") || "1",
colorId: getValueFromQuery(query.color) || "8", colorId: searchParams.get("color") || "8",
pose: getValueFromQuery(query.pose) || "HAPPY_FEM", pose: searchParams.get("pose") || "HAPPY_FEM",
appearanceId: getValueFromQuery(query.state) || null, appearanceId: searchParams.get("state") || null,
wornItemIds: new Set(getListFromQuery(query["objects[]"])), wornItemIds: new Set(searchParams.getAll("objects[]")),
closetedItemIds: new Set(getListFromQuery(query["closet[]"])), closetedItemIds: new Set(searchParams.getAll("closet[]")),
}; };
} }
/**
* getValueFromQuery reads the given value from Next's `router.query` as a
* single value. For example:
*
* ?foo=bar -> "bar" -> "bar"
* ?foo=bar&foo=baz -> ["bar", "baz"] -> "bar"
* ?lol=huh -> undefined -> null
*/
function getValueFromQuery(value) {
if (Array.isArray(value)) {
return value[0];
} else if (value != null) {
return value;
} else {
return null;
}
}
/**
* getListFromQuery reads the given value from Next's `router.query` as a list
* of values. For example:
*
* ?foo=bar -> "bar" -> ["bar"]
* ?foo=bar&foo=baz -> ["bar", "baz"] -> ["bar", "baz"]
* ?lol=huh -> undefined -> []
*/
function getListFromQuery(value) {
if (Array.isArray(value)) {
return value;
} else if (value != null) {
return [value];
} else {
return [];
}
}
function getOutfitStateFromOutfitData(outfit) { function getOutfitStateFromOutfitData(outfit) {
if (!outfit) { if (!outfit) {
return EMPTY_CUSTOMIZATION_STATE; return EMPTY_CUSTOMIZATION_STATE;

View file

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { Box, Button, Flex, Select } from "@chakra-ui/react"; import { Box, Button, Flex, Select } from "@chakra-ui/react";
import { useRouter } from "next/router"; import { useSearchParams } from "react-router-dom";
function PaginationToolbar({ function PaginationToolbar({
isLoading, isLoading,
@ -72,9 +72,9 @@ function PaginationToolbar({
} }
export function useRouterPagination(totalCount, numPerPage) { export function useRouterPagination(totalCount, numPerPage) {
const { query, push: pushHistory } = useRouter(); const [searchParams, setSearchParams] = useSearchParams();
const currentOffset = parseInt(query.offset) || 0; const currentOffset = parseInt(searchParams.get("offset")) || 0;
const currentPageIndex = Math.floor(currentOffset / numPerPage); const currentPageIndex = Math.floor(currentOffset / numPerPage);
const currentPageNumber = currentPageIndex + 1; const currentPageNumber = currentPageIndex + 1;
@ -82,11 +82,12 @@ export function useRouterPagination(totalCount, numPerPage) {
const buildPageUrl = React.useCallback( const buildPageUrl = React.useCallback(
(newPageNumber) => { (newPageNumber) => {
const newParams = new URLSearchParams(query); setSearchParams((newParams) => {
const newPageIndex = newPageNumber - 1; const newPageIndex = newPageNumber - 1;
const newOffset = newPageIndex * numPerPage; const newOffset = newPageIndex * numPerPage;
newParams.set("offset", newOffset); newParams.set("offset", newOffset);
return "?" + newParams.toString(); return newParams;
});
}, },
[query, numPerPage] [query, numPerPage]
); );

View file

@ -225,7 +225,6 @@ function ItemThumbnail({ item, tradeMatchingMode }) {
position: relative; position: relative;
`} `}
> >
{/* eslint-disable-next-line @next/next/no-img-element */}
<img <img
src={safeImageUrl(item.thumbnailUrl, { preferArchive })} src={safeImageUrl(item.thumbnailUrl, { preferArchive })}
alt={`Thumbnail art for ${item.name}`} alt={`Thumbnail art for ${item.name}`}

View file

@ -41,7 +41,8 @@ OpenneoImpressItems::Application.routes.draw do
resources :neopets_users, :only => [:new, :create], :path => 'neopets-users' resources :neopets_users, :only => [:new, :create], :path => 'neopets-users'
end end
get '/users/current-user/outfits' => 'outfits#index', :as => :current_user_outfits get '/your-outfits', to: 'outfits#index', as: :current_user_outfits
get '/users/current-user/outfits', to: redirect('/your-outfits')
post '/pets/load' => 'pets#load', :as => :load_pet post '/pets/load' => 'pets#load', :as => :load_pet
post '/pets/submit' => 'pets#submit', :method => :post post '/pets/submit' => 'pets#submit', :method => :post