diff --git a/src/app/App.js b/src/app/App.js
index 398dbf7..cc544d1 100644
--- a/src/app/App.js
+++ b/src/app/App.js
@@ -30,6 +30,9 @@ const ItemTradesSeekingPage = loadable(() =>
);
const ModelingPage = loadable(() => import("./ModelingPage"));
const PrivacyPolicyPage = loadable(() => import("./PrivacyPolicyPage"));
+const SupportPetAppearancesPage = loadable(() =>
+ import("./SupportPetAppearancesPage")
+);
const UserItemsPage = loadable(() => import("./UserItemsPage"));
const UserOutfitsPage = loadable(() => import("./UserOutfitsPage"));
const WardrobePage = loadable(() => import("./WardrobePage"), {
@@ -156,6 +159,11 @@ function App() {
+
+
+
+
+
diff --git a/src/app/SupportPetAppearancesPage.js b/src/app/SupportPetAppearancesPage.js
new file mode 100644
index 0000000..a73ee21
--- /dev/null
+++ b/src/app/SupportPetAppearancesPage.js
@@ -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 (
+ <>
+ Support: Pet appearances
+
+ >
+ );
+}
+
+function UnlabeledPetAppearancesList() {
+ const { loading, error, speciesColorPairs } = useUnlabeledPetAppearances();
+
+ return (
+
+
+ These pet appearances have some UNKNOWN
poses that need
+ labeled! Please take a look!
+
+ {loading && (
+
+
+
+ )}
+ {error && {error.message}}
+ {speciesColorPairs.length > 0 && (
+
+ {speciesColorPairs.map(({ species, color }) => (
+
+
+
+ ))}
+
+ )}
+
+ );
+}
+
+function SpeciesColorEditorLink({ species, color }) {
+ return (
+
+ {color.name} {species.name}
+
+ );
+}
+
+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;
diff --git a/src/app/components/SpeciesColorPicker.js b/src/app/components/SpeciesColorPicker.js
index 9e7a8f3..3cd4c1b 100644
--- a/src/app/components/SpeciesColorPicker.js
+++ b/src/app/components/SpeciesColorPicker.js
@@ -310,10 +310,10 @@ const SpeciesColorSelect = ({
);
};
-export function useAllValidPetPoses() {
+export function useAllValidPetPoses(fetchOptions) {
const { loading, error, data: validsBuffer } = useFetch(
"/api/validPetPoses",
- { responseType: "arrayBuffer" }
+ { ...fetchOptions, responseType: "arrayBuffer" }
);
const valids = React.useMemo(
diff --git a/src/app/util.js b/src/app/util.js
index 9bc7b6b..b02d0b0 100644
--- a/src/app/util.js
+++ b/src/app/util.js
@@ -227,7 +227,7 @@ export function usePageTitle(title, { skip = false } = {}) {
*
* 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
// fetch non-binary data later, extend this and get something else from res!
if (responseType !== "arrayBuffer") {
@@ -241,7 +241,7 @@ export function useFetch(url, { responseType }) {
React.useEffect(() => {
let canceled = false;
- fetch(url)
+ fetch(url, { headers })
.then(async (res) => {
if (canceled) {
return;