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 { css } from "emotion";
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
import { Box, Button, Flex, Input, useTheme, useToast } from "@chakra-ui/core";
|
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 { useLazyQuery } from "@apollo/client";
|
||||||
|
|
||||||
import { Heading1, usePageTitle } from "./util";
|
import { Heading1, usePageTitle } from "./util";
|
||||||
|
@ -14,6 +14,7 @@ import SpeciesColorPicker from "./components/SpeciesColorPicker";
|
||||||
|
|
||||||
function HomePage() {
|
function HomePage() {
|
||||||
usePageTitle("Dress to Impress");
|
usePageTitle("Dress to Impress");
|
||||||
|
useSupportSetup();
|
||||||
|
|
||||||
const [previewState, setPreviewState] = React.useState(null);
|
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;
|
export default HomePage;
|
||||||
|
|
|
@ -9,9 +9,15 @@ import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@chakra-ui/core";
|
} 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 { 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!
|
* Item show a basic summary of an item, in the context of the current outfit!
|
||||||
|
@ -29,10 +35,16 @@ export function Item({ item, itemNameId, outfitState, dispatchToOutfit }) {
|
||||||
const isWorn = wornItemIds.includes(item.id);
|
const isWorn = wornItemIds.includes(item.id);
|
||||||
const isInOutfit = allItemIds.includes(item.id);
|
const isInOutfit = allItemIds.includes(item.id);
|
||||||
|
|
||||||
|
const [supportDrawerIsOpen, setSupportDrawerIsOpen] = React.useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<ItemContainer>
|
<ItemContainer>
|
||||||
<Box>
|
<Box>
|
||||||
<ItemThumbnail src={safeImageUrl(item.thumbnailUrl)} isWorn={isWorn} />
|
<ItemThumbnail
|
||||||
|
src={safeImageUrl(item.thumbnailUrl)}
|
||||||
|
isWorn={isWorn}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box width="3" />
|
<Box width="3" />
|
||||||
<Box>
|
<Box>
|
||||||
|
@ -42,10 +54,17 @@ export function Item({ item, itemNameId, outfitState, dispatchToOutfit }) {
|
||||||
</Box>
|
</Box>
|
||||||
<Box flexGrow="1" />
|
<Box flexGrow="1" />
|
||||||
<Box>
|
<Box>
|
||||||
|
<SupportOnly>
|
||||||
|
<ItemActionButton
|
||||||
|
icon={<EditIcon />}
|
||||||
|
label="Edit"
|
||||||
|
onClick={() => setSupportDrawerIsOpen(true)}
|
||||||
|
/>
|
||||||
|
</SupportOnly>
|
||||||
<ItemActionButton
|
<ItemActionButton
|
||||||
icon={<InfoIcon />}
|
icon={<InfoIcon />}
|
||||||
label="More info"
|
label="More info"
|
||||||
href={`http://impress.openneo.net/items/${
|
href={`https://impress.openneo.net/items/${
|
||||||
item.id
|
item.id
|
||||||
}-${item.name.replace(/ /g, "-")}`}
|
}-${item.name.replace(/ /g, "-")}`}
|
||||||
/>
|
/>
|
||||||
|
@ -60,6 +79,14 @@ export function Item({ item, itemNameId, outfitState, dispatchToOutfit }) {
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</ItemContainer>
|
</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