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:
parent
93760db1e0
commit
b310f2334d
5 changed files with 207 additions and 31 deletions
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
80
src/app/WardrobePage/support/ItemSupportDrawer.js
Normal file
80
src/app/WardrobePage/support/ItemSupportDrawer.js
Normal 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;
|
0
src/app/WardrobePage/support/ItemSupportDrawerContent.js
Normal file
0
src/app/WardrobePage/support/ItemSupportDrawerContent.js
Normal file
24
src/app/WardrobePage/support/SupportOnly.js
Normal file
24
src/app/WardrobePage/support/SupportOnly.js
Normal 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;
|
Loading…
Reference in a new issue