draft UI for item support drawer

Special color is mocked out, but not backed by real data or actually changeable!
This commit is contained in:
Emi Matchu 2020-07-31 16:57:37 -07:00
parent 93760db1e0
commit b310f2334d
5 changed files with 207 additions and 31 deletions

View file

@ -2,7 +2,7 @@ import React from "react";
import { css } from "emotion";
import gql from "graphql-tag";
import { Box, Button, Flex, Input, useTheme, useToast } from "@chakra-ui/core";
import { useHistory } from "react-router-dom";
import { useHistory, useLocation } from "react-router-dom";
import { useLazyQuery } from "@apollo/client";
import { Heading1, usePageTitle } from "./util";
@ -14,6 +14,7 @@ import SpeciesColorPicker from "./components/SpeciesColorPicker";
function HomePage() {
usePageTitle("Dress to Impress");
useSupportSetup();
const [previewState, setPreviewState] = React.useState(null);
@ -226,4 +227,48 @@ function SubmitPetForm() {
);
}
/**
* useSupportSetup helps our support staff get set up with special access.
* If you provide ?supportSecret=... in the URL, we'll save it in a cookie and
* pop up a toast!
*
* This doesn't guarantee the secret is correct, of course! We don't bother to
* check that here; the server will reject requests from bad support secrets.
* And there's nothing especially secret in the support UI, so it's okay if
* other people know about the tools and poke around a powerless interface!
*/
function useSupportSetup() {
const location = useLocation();
const toast = useToast();
React.useEffect(() => {
const params = new URLSearchParams(location.search);
const supportSecret = params.get("supportSecret");
if (supportSecret) {
localStorage.setItem("supportSecret", supportSecret);
toast({
title: "Support secret saved!",
description:
`You should now see special Support UI across the site. ` +
`Thanks for your help! 💖`,
status: "success",
duration: 10000,
isClosable: true,
});
} else if (supportSecret === "") {
localStorage.removeItem("supportSecret");
toast({
title: "Support secret deleted.",
description: `The Support UI will now stop appearing on this device.`,
status: "success",
duration: 10000,
isClosable: true,
});
}
}, [location, toast]);
}
export default HomePage;

View file

@ -9,9 +9,15 @@ import {
Tooltip,
useTheme,
} from "@chakra-ui/core";
import { DeleteIcon, InfoIcon } from "@chakra-ui/icons";
import { EditIcon, DeleteIcon, InfoIcon } from "@chakra-ui/icons";
import loadable from "@loadable/component";
import { safeImageUrl } from "../util";
import SupportOnly from "./support/SupportOnly";
const LoadableItemSupportDrawer = loadable(() =>
import("./support/ItemSupportDrawer")
);
/**
* Item show a basic summary of an item, in the context of the current outfit!
@ -29,37 +35,58 @@ export function Item({ item, itemNameId, outfitState, dispatchToOutfit }) {
const isWorn = wornItemIds.includes(item.id);
const isInOutfit = allItemIds.includes(item.id);
const [supportDrawerIsOpen, setSupportDrawerIsOpen] = React.useState(false);
return (
<ItemContainer>
<Box>
<ItemThumbnail src={safeImageUrl(item.thumbnailUrl)} isWorn={isWorn} />
</Box>
<Box width="3" />
<Box>
<ItemName id={itemNameId} isWorn={isWorn}>
{item.name}
</ItemName>
</Box>
<Box flexGrow="1" />
<Box>
<ItemActionButton
icon={<InfoIcon />}
label="More info"
href={`http://impress.openneo.net/items/${
item.id
}-${item.name.replace(/ /g, "-")}`}
/>
{isInOutfit && (
<ItemActionButton
icon={<DeleteIcon />}
label="Remove"
onClick={() =>
dispatchToOutfit({ type: "removeItem", itemId: item.id })
}
<>
<ItemContainer>
<Box>
<ItemThumbnail
src={safeImageUrl(item.thumbnailUrl)}
isWorn={isWorn}
/>
)}
</Box>
</ItemContainer>
</Box>
<Box width="3" />
<Box>
<ItemName id={itemNameId} isWorn={isWorn}>
{item.name}
</ItemName>
</Box>
<Box flexGrow="1" />
<Box>
<SupportOnly>
<ItemActionButton
icon={<EditIcon />}
label="Edit"
onClick={() => setSupportDrawerIsOpen(true)}
/>
</SupportOnly>
<ItemActionButton
icon={<InfoIcon />}
label="More info"
href={`https://impress.openneo.net/items/${
item.id
}-${item.name.replace(/ /g, "-")}`}
/>
{isInOutfit && (
<ItemActionButton
icon={<DeleteIcon />}
label="Remove"
onClick={() =>
dispatchToOutfit({ type: "removeItem", itemId: item.id })
}
/>
)}
</Box>
</ItemContainer>
<SupportOnly>
<LoadableItemSupportDrawer
item={item}
isOpen={supportDrawerIsOpen}
onClose={() => setSupportDrawerIsOpen(false)}
/>
</SupportOnly>
</>
);
}

View file

@ -0,0 +1,80 @@
import * as React from "react";
import {
Badge,
Box,
Drawer,
DrawerBody,
DrawerCloseButton,
DrawerContent,
DrawerHeader,
DrawerOverlay,
FormControl,
FormHelperText,
FormLabel,
Link,
Select,
} from "@chakra-ui/core";
import { ExternalLinkIcon } from "@chakra-ui/icons";
/**
* ItemSupportDrawer shows Support UI for the item when open.
*
* This component controls the drawer element. The actual content is imported
* from another lazy-loaded component!
*/
function ItemSupportDrawer({ item, isOpen, onClose }) {
return (
<Drawer
placement="bottom"
isOpen={isOpen}
onClose={onClose}
// blockScrollOnMount doesn't matter on our fullscreen UI, but the
// default implementation breaks out layout somehow 🤔 idk, let's not!
blockScrollOnMount={false}
>
<DrawerOverlay>
<DrawerContent>
<DrawerCloseButton />
<DrawerHeader>
{item.name}
<Badge colorScheme="purple" marginLeft="3">
Support <span aria-hidden="true">💖</span>
</Badge>
</DrawerHeader>
<DrawerBody>
<Box paddingBottom="5">
<SpecialColorFields item={item} />
</Box>
</DrawerBody>
</DrawerContent>
</DrawerOverlay>
</Drawer>
);
}
function SpecialColorFields({ item }) {
return (
<FormControl>
<FormLabel>Special color</FormLabel>
<Select placeholder="Default: Auto-detect from item description">
<option>Mutant</option>
<option>Maraquan</option>
</Select>
<FormHelperText>
This controls which previews we show on the{" "}
<Link
href={`https://impress.openneo.net/items/${
item.id
}-${item.name.replace(/ /g, "-")}`}
color="green.500"
isExternal
>
item page <ExternalLinkIcon />
</Link>
.
</FormHelperText>
</FormControl>
);
}
export default ItemSupportDrawer;

View file

@ -0,0 +1,24 @@
import * as React from "react";
/**
* SupportOnly only shows its contents to Support users. For most users, the
* content will be hidden!
*
* To become a Support user, you visit /?supportSecret=..., which saves the
* secret to your device.
*
* Note that this component doesn't check that the secret is *correct*, so it's
* possible to view this UI by faking an invalid secret. That's okay, because
* the server checks the provided secret for each Support request.
*/
function SupportOnly({ children }) {
const supportSecret = React.useMemo(getSupportSecret, []);
return supportSecret ? children : null;
}
function getSupportSecret() {
return localStorage.getItem("supportSecret");
}
export default SupportOnly;