impress-2020/src/app/UserOutfitsPage.js

196 lines
4.5 KiB
JavaScript
Raw Normal View History

import React from "react";
import { Box, Center, Flex, Wrap, WrapItem } from "@chakra-ui/react";
2021-01-08 00:35:56 -08:00
import { ClassNames } from "@emotion/react";
import gql from "graphql-tag";
import { useQuery } from "@apollo/client";
import { Link } from "react-router-dom";
import { Heading1, MajorErrorMessage, useCommonStyles } from "./util";
import HangerSpinner from "./components/HangerSpinner";
import OutfitThumbnail from "./components/OutfitThumbnail";
import useRequireLogin from "./components/useRequireLogin";
2021-01-04 00:12:25 -08:00
import WIPCallout from "./components/WIPCallout";
function UserOutfitsPage() {
return (
<Box>
2021-01-04 00:12:25 -08:00
<Flex justifyContent="space-between" marginBottom="4">
<Heading1>Your outfits</Heading1>
<WIPCallout details="This list doesn't work well with a lot of outfits yet. We'll paginate it soon! And starred outfits are coming, too!" />
2021-01-04 00:12:25 -08:00
</Flex>
<UserOutfitsPageContent />
</Box>
);
}
function UserOutfitsPageContent() {
const { isLoading: userLoading } = useRequireLogin();
const { loading: queryLoading, error, data } = useQuery(
gql`
query UserOutfitsPageContent {
currentUser {
id
outfits {
id
2021-01-03 23:36:00 -08:00
name
updatedAt
# For alt text
2021-01-03 23:36:00 -08:00
petAppearance {
species {
id
name
}
color {
id
name
}
}
wornItems {
id
name
}
}
}
}
`,
{
context: { sendAuth: true },
skip: userLoading,
}
);
if (userLoading || queryLoading) {
return (
<Center>
<HangerSpinner />
</Center>
);
}
if (error) {
return <MajorErrorMessage error={error} variant="network" />;
}
const outfits = data.currentUser.outfits;
if (outfits.length === 0) {
return (
<Box>You don't have any outfits yet. Maybe you can create some!</Box>
);
}
return (
<Wrap spacing="4" justify="space-around">
{outfits.map((outfit) => (
<WrapItem key={outfit.id}>
<OutfitCard outfit={outfit} />
</WrapItem>
))}
</Wrap>
);
}
function OutfitCard({ outfit }) {
const image = (
2021-01-08 00:35:56 -08:00
<ClassNames>
{({ css }) => (
<OutfitThumbnail
outfitId={outfit.id}
updatedAt={outfit.updatedAt}
2021-01-08 00:35:56 -08:00
alt={buildOutfitAltText(outfit)}
// Firefox shows alt text as a fallback for images it can't show yet.
// Show our alt text clearly if the image failed to load... but hide
// it if it's still loading. It's normal for these to take a second
// to load on a new device, and the flash of text is unhelpful.
color="white"
fontSize="xs"
width={150}
height={150}
2021-01-08 00:35:56 -08:00
overflow="auto"
loading="lazy"
2021-01-08 00:35:56 -08:00
className={css`
&:-moz-loading {
visibility: hidden;
}
&:-moz-broken {
padding: 0.5rem;
}
2021-01-08 00:35:56 -08:00
`}
/>
)}
2021-01-08 00:35:56 -08:00
</ClassNames>
);
return (
<Box
as={Link}
to={`/outfits/${outfit.id}`}
display="block"
transition="all 0.2s"
_hover={{ transform: `scale(1.05)` }}
_focus={{
transform: `scale(1.05)`,
boxShadow: "outline",
outline: "none",
}}
>
<OutfitCardLayout image={image} caption={outfit.name} />
</Box>
);
}
function OutfitCardLayout({ image, caption }) {
const { brightBackground } = useCommonStyles();
2021-01-04 00:45:52 -08:00
return (
<Flex
direction="column"
alignItems="center"
textAlign="center"
boxShadow="md"
borderRadius="md"
padding="3"
width="calc(150px + 2em)"
backgroundColor={brightBackground}
2021-01-04 00:45:52 -08:00
transition="all 0.2s"
>
2021-01-04 00:39:15 -08:00
<Box
width={150}
height={150}
marginBottom="2"
borderRadius="md"
background="gray.600"
overflow="hidden"
>
{image}
</Box>
<Box>{caption}</Box>
</Flex>
);
}
2021-01-08 00:35:56 -08:00
function buildOutfitAltText(outfit) {
const { petAppearance, wornItems } = outfit;
const { species, color } = petAppearance;
let altText = "";
const petDescription = `${color.name} ${species.name}`;
altText += petDescription;
if (wornItems.length > 0) {
const itemNames = wornItems
.map((item) => item.name)
.sort()
.join(", ");
altText += ` wearing ${itemNames}`;
}
return altText;
}
export default UserOutfitsPage;