Add /support/petAppearances page

This commit is contained in:
Emi Matchu 2021-04-23 11:31:25 -07:00
parent 8b5ba60ea8
commit aa28ee8b39
4 changed files with 138 additions and 4 deletions

View file

@ -30,6 +30,9 @@ const ItemTradesSeekingPage = loadable(() =>
); );
const ModelingPage = loadable(() => import("./ModelingPage")); const ModelingPage = loadable(() => import("./ModelingPage"));
const PrivacyPolicyPage = loadable(() => import("./PrivacyPolicyPage")); const PrivacyPolicyPage = loadable(() => import("./PrivacyPolicyPage"));
const SupportPetAppearancesPage = loadable(() =>
import("./SupportPetAppearancesPage")
);
const UserItemsPage = loadable(() => import("./UserItemsPage")); const UserItemsPage = loadable(() => import("./UserItemsPage"));
const UserOutfitsPage = loadable(() => import("./UserOutfitsPage")); const UserOutfitsPage = loadable(() => import("./UserOutfitsPage"));
const WardrobePage = loadable(() => import("./WardrobePage"), { const WardrobePage = loadable(() => import("./WardrobePage"), {
@ -156,6 +159,11 @@ function App() {
<ConversionPage /> <ConversionPage />
</PageLayout> </PageLayout>
</Route> </Route>
<Route path="/support/petAppearances">
<PageLayout>
<SupportPetAppearancesPage />
</PageLayout>
</Route>
<Route path="/"> <Route path="/">
<PageLayout hideHomeLink> <PageLayout hideHomeLink>
<HomePage /> <HomePage />

View file

@ -0,0 +1,126 @@
import { gql, useQuery } from "@apollo/client";
import { Box, Flex, Wrap, WrapItem } from "@chakra-ui/layout";
import { Link } from "react-router-dom";
import HangerSpinner from "./components/HangerSpinner";
import {
getValidPoses,
useAllValidPetPoses,
} from "./components/SpeciesColorPicker";
import { ErrorMessage, Heading1 } from "./util";
import useSupport from "./WardrobePage/support/useSupport";
function SupportPetAppearancesPage() {
const { isSupportUser } = useSupport();
if (!isSupportUser) {
return "Sorry, this page is only for Support users!";
}
return (
<>
<Heading1 marginBottom=".5em">Support: Pet appearances</Heading1>
<UnlabeledPetAppearancesList />
</>
);
}
function UnlabeledPetAppearancesList() {
const { loading, error, speciesColorPairs } = useUnlabeledPetAppearances();
return (
<Box>
<Box as="p" marginBottom="2">
These pet appearances have some <code>UNKNOWN</code> poses that need
labeled! Please take a look!
</Box>
{loading && (
<Flex justify="center">
<HangerSpinner />
</Flex>
)}
{error && <ErrorMessage>{error.message}</ErrorMessage>}
{speciesColorPairs.length > 0 && (
<Wrap>
{speciesColorPairs.map(({ species, color }) => (
<WrapItem key={`${species.id}-${color.id}`}>
<SpeciesColorEditorLink species={species} color={color} />
</WrapItem>
))}
</Wrap>
)}
</Box>
);
}
function SpeciesColorEditorLink({ species, color }) {
return (
<Box
as={Link}
to={`/outfits/new?species=${species.id}&color=${color.id}`}
target="supportPetAppearanceEditor"
border="1px solid"
borderColor="green.600"
borderRadius="full"
paddingX="3"
paddingY="2"
fontSize="sm"
>
{color.name} {species.name}
</Box>
);
}
function useUnlabeledPetAppearances() {
const {
loading: loadingValids,
error: errorValids,
valids,
} = useAllValidPetPoses({ headers: { "Cache-Control": "no-cache" } });
const { loading: loadingGQL, error: errorGQL, data } = useQuery(gql`
query SupportUnlabeledPetAppearances {
allColors {
id
name
}
allSpecies {
id
name
}
}
`);
const loading = loadingValids || loadingGQL;
const error = errorValids || errorGQL;
const speciesColorPairs =
valids && data?.allColors && data?.allSpecies
? data?.allSpecies
.map((species) => data.allColors.map((color) => ({ species, color })))
.flat()
.filter(({ species, color }) => {
const poses = getValidPoses(valids, species.id, color.id);
const hasAllStandardPoses =
poses.has("HAPPY_MASC") &&
poses.has("HAPPY_FEM") &&
poses.has("SAD_MASC") &&
poses.has("SAD_FEM") &&
poses.has("SICK_MASC") &&
poses.has("SICK_FEM");
const hasAtLeastOneUnknownPose = poses.has("UNKNOWN");
return !hasAllStandardPoses && hasAtLeastOneUnknownPose;
})
.sort((a, b) =>
`${a.species.name} ${a.color.name}`.localeCompare(
`${b.species.name} ${b.color.name}`
)
)
: [];
return {
loading,
error,
speciesColorPairs,
};
}
export default SupportPetAppearancesPage;

View file

@ -310,10 +310,10 @@ const SpeciesColorSelect = ({
); );
}; };
export function useAllValidPetPoses() { export function useAllValidPetPoses(fetchOptions) {
const { loading, error, data: validsBuffer } = useFetch( const { loading, error, data: validsBuffer } = useFetch(
"/api/validPetPoses", "/api/validPetPoses",
{ responseType: "arrayBuffer" } { ...fetchOptions, responseType: "arrayBuffer" }
); );
const valids = React.useMemo( const valids = React.useMemo(

View file

@ -227,7 +227,7 @@ export function usePageTitle(title, { skip = false } = {}) {
* *
* Our limited API is designed to match the `use-http` library! * Our limited API is designed to match the `use-http` library!
*/ */
export function useFetch(url, { responseType }) { export function useFetch(url, { headers = {}, responseType }) {
// Just trying to be clear about what you'll get back ^_^` If we want to // Just trying to be clear about what you'll get back ^_^` If we want to
// fetch non-binary data later, extend this and get something else from res! // fetch non-binary data later, extend this and get something else from res!
if (responseType !== "arrayBuffer") { if (responseType !== "arrayBuffer") {
@ -241,7 +241,7 @@ export function useFetch(url, { responseType }) {
React.useEffect(() => { React.useEffect(() => {
let canceled = false; let canceled = false;
fetch(url) fetch(url, { headers })
.then(async (res) => { .then(async (res) => {
if (canceled) { if (canceled) {
return; return;