impress-2020/src/app/UserOutfitsPage.js

244 lines
6 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 "next/link";
import { useRouter } from "next/router";
import { Heading1, MajorErrorMessage, useCommonStyles } from "./util";
import HangerSpinner from "./components/HangerSpinner";
import OutfitThumbnail from "./components/OutfitThumbnail";
Paginate the user outfits page My main inspiration for doing this is actually our potentially-huge upcoming Vercel bill lol From inspecting my Honeycomb dashboard, it looks like the main offender for backend CPU time usage is outfit images. And it looks like they come in big spikes, of lots of low usage and then suddenly 1,000 requests in one minute. My suspicion is that this is from users with many saved outfits loading their outfit page, which previously would show all of them at once. We do have `loading="lazy"` set, but not all browsers support that yet, and I've had trouble pinning down the exact behavior anyway! Anyway, paginating makes for a better experience for those huge-list users anyway. We've been meaning to do it, so here we go! My hope is that this drastically decreases backend CPU hours immediately 🤞 If not, we'll need to investigate in more detail where these outfit image requests are actually coming from! Note that I added the pagination to the existing `outfits` GraphQL endpoint, rather than creating a new one. I felt comfortable doing this because it requires login anyway, so I'm confident that other clients aren't using it; and because, while this kind of thing often creates a risk of problems with frontend and backend code getting out of sync, I think someone running old frontend code will just see only their first 30 outfits (but no pagination toolbar), and get confused and refresh the page, at which point they'll see all of them. (And I actually _prefer_ that slightly confusing UX, to avoid getting more giant spikes of outfit image requests, lol :p)
2021-11-01 19:33:40 -07:00
import PaginationToolbar from "./components/PaginationToolbar";
import useCurrentUser from "./components/useCurrentUser";
function UserOutfitsPage() {
return (
<Box>
Paginate the user outfits page My main inspiration for doing this is actually our potentially-huge upcoming Vercel bill lol From inspecting my Honeycomb dashboard, it looks like the main offender for backend CPU time usage is outfit images. And it looks like they come in big spikes, of lots of low usage and then suddenly 1,000 requests in one minute. My suspicion is that this is from users with many saved outfits loading their outfit page, which previously would show all of them at once. We do have `loading="lazy"` set, but not all browsers support that yet, and I've had trouble pinning down the exact behavior anyway! Anyway, paginating makes for a better experience for those huge-list users anyway. We've been meaning to do it, so here we go! My hope is that this drastically decreases backend CPU hours immediately 🤞 If not, we'll need to investigate in more detail where these outfit image requests are actually coming from! Note that I added the pagination to the existing `outfits` GraphQL endpoint, rather than creating a new one. I felt comfortable doing this because it requires login anyway, so I'm confident that other clients aren't using it; and because, while this kind of thing often creates a risk of problems with frontend and backend code getting out of sync, I think someone running old frontend code will just see only their first 30 outfits (but no pagination toolbar), and get confused and refresh the page, at which point they'll see all of them. (And I actually _prefer_ that slightly confusing UX, to avoid getting more giant spikes of outfit image requests, lol :p)
2021-11-01 19:33:40 -07:00
<Heading1 marginBottom="4">Your outfits</Heading1>
<UserOutfitsPageContent />
</Box>
);
}
const USER_OUTFITS_PAGE_QUERY = gql`
query UserOutfitsPageContent($offset: Int!) {
currentUser {
id
numTotalOutfits
outfits(limit: 20, offset: $offset) {
id
name
updatedAt
# For alt text
petAppearance {
species {
id
name
}
color {
id
name
}
}
wornItems {
id
name
}
}
}
}
`;
const PER_PAGE = 20;
function UserOutfitsPageContent() {
const { isLoggedIn, isLoading: userLoading } = useCurrentUser();
const { query } = useRouter();
const offset = parseInt(query.offset) || 0;
Paginate the user outfits page My main inspiration for doing this is actually our potentially-huge upcoming Vercel bill lol From inspecting my Honeycomb dashboard, it looks like the main offender for backend CPU time usage is outfit images. And it looks like they come in big spikes, of lots of low usage and then suddenly 1,000 requests in one minute. My suspicion is that this is from users with many saved outfits loading their outfit page, which previously would show all of them at once. We do have `loading="lazy"` set, but not all browsers support that yet, and I've had trouble pinning down the exact behavior anyway! Anyway, paginating makes for a better experience for those huge-list users anyway. We've been meaning to do it, so here we go! My hope is that this drastically decreases backend CPU hours immediately 🤞 If not, we'll need to investigate in more detail where these outfit image requests are actually coming from! Note that I added the pagination to the existing `outfits` GraphQL endpoint, rather than creating a new one. I felt comfortable doing this because it requires login anyway, so I'm confident that other clients aren't using it; and because, while this kind of thing often creates a risk of problems with frontend and backend code getting out of sync, I think someone running old frontend code will just see only their first 30 outfits (but no pagination toolbar), and get confused and refresh the page, at which point they'll see all of them. (And I actually _prefer_ that slightly confusing UX, to avoid getting more giant spikes of outfit image requests, lol :p)
2021-11-01 19:33:40 -07:00
const { loading: queryLoading, error, data } = useQuery(
USER_OUTFITS_PAGE_QUERY,
{
Paginate the user outfits page My main inspiration for doing this is actually our potentially-huge upcoming Vercel bill lol From inspecting my Honeycomb dashboard, it looks like the main offender for backend CPU time usage is outfit images. And it looks like they come in big spikes, of lots of low usage and then suddenly 1,000 requests in one minute. My suspicion is that this is from users with many saved outfits loading their outfit page, which previously would show all of them at once. We do have `loading="lazy"` set, but not all browsers support that yet, and I've had trouble pinning down the exact behavior anyway! Anyway, paginating makes for a better experience for those huge-list users anyway. We've been meaning to do it, so here we go! My hope is that this drastically decreases backend CPU hours immediately 🤞 If not, we'll need to investigate in more detail where these outfit image requests are actually coming from! Note that I added the pagination to the existing `outfits` GraphQL endpoint, rather than creating a new one. I felt comfortable doing this because it requires login anyway, so I'm confident that other clients aren't using it; and because, while this kind of thing often creates a risk of problems with frontend and backend code getting out of sync, I think someone running old frontend code will just see only their first 30 outfits (but no pagination toolbar), and get confused and refresh the page, at which point they'll see all of them. (And I actually _prefer_ that slightly confusing UX, to avoid getting more giant spikes of outfit image requests, lol :p)
2021-11-01 19:33:40 -07:00
variables: { offset },
context: { sendAuth: true },
skip: !isLoggedIn,
Paginate the user outfits page My main inspiration for doing this is actually our potentially-huge upcoming Vercel bill lol From inspecting my Honeycomb dashboard, it looks like the main offender for backend CPU time usage is outfit images. And it looks like they come in big spikes, of lots of low usage and then suddenly 1,000 requests in one minute. My suspicion is that this is from users with many saved outfits loading their outfit page, which previously would show all of them at once. We do have `loading="lazy"` set, but not all browsers support that yet, and I've had trouble pinning down the exact behavior anyway! Anyway, paginating makes for a better experience for those huge-list users anyway. We've been meaning to do it, so here we go! My hope is that this drastically decreases backend CPU hours immediately 🤞 If not, we'll need to investigate in more detail where these outfit image requests are actually coming from! Note that I added the pagination to the existing `outfits` GraphQL endpoint, rather than creating a new one. I felt comfortable doing this because it requires login anyway, so I'm confident that other clients aren't using it; and because, while this kind of thing often creates a risk of problems with frontend and backend code getting out of sync, I think someone running old frontend code will just see only their first 30 outfits (but no pagination toolbar), and get confused and refresh the page, at which point they'll see all of them. (And I actually _prefer_ that slightly confusing UX, to avoid getting more giant spikes of outfit image requests, lol :p)
2021-11-01 19:33:40 -07:00
// This will give us the cached numTotalOutfits while we wait for the
// next page!
returnPartialData: true,
}
);
const numTotalOutfits = data?.currentUser?.numTotalOutfits || null;
// Preload the previous and next pages. (Sigh, if we were doing cool Next.js
// stuff, this would already be happening by next/link magic I think!)
const prevPageOffset = offset - PER_PAGE;
const nextPageOffset = offset + PER_PAGE;
useQuery(USER_OUTFITS_PAGE_QUERY, {
variables: { offset: prevPageOffset },
context: { sendAuth: true },
skip: userLoading || offset === 0 || prevPageOffset < 0,
});
useQuery(USER_OUTFITS_PAGE_QUERY, {
variables: { offset: nextPageOffset },
context: { sendAuth: true },
skip:
userLoading ||
numTotalOutfits == null ||
nextPageOffset >= numTotalOutfits,
});
Paginate the user outfits page My main inspiration for doing this is actually our potentially-huge upcoming Vercel bill lol From inspecting my Honeycomb dashboard, it looks like the main offender for backend CPU time usage is outfit images. And it looks like they come in big spikes, of lots of low usage and then suddenly 1,000 requests in one minute. My suspicion is that this is from users with many saved outfits loading their outfit page, which previously would show all of them at once. We do have `loading="lazy"` set, but not all browsers support that yet, and I've had trouble pinning down the exact behavior anyway! Anyway, paginating makes for a better experience for those huge-list users anyway. We've been meaning to do it, so here we go! My hope is that this drastically decreases backend CPU hours immediately 🤞 If not, we'll need to investigate in more detail where these outfit image requests are actually coming from! Note that I added the pagination to the existing `outfits` GraphQL endpoint, rather than creating a new one. I felt comfortable doing this because it requires login anyway, so I'm confident that other clients aren't using it; and because, while this kind of thing often creates a risk of problems with frontend and backend code getting out of sync, I think someone running old frontend code will just see only their first 30 outfits (but no pagination toolbar), and get confused and refresh the page, at which point they'll see all of them. (And I actually _prefer_ that slightly confusing UX, to avoid getting more giant spikes of outfit image requests, lol :p)
2021-11-01 19:33:40 -07:00
const isLoading = userLoading || queryLoading;
if (error) {
return <MajorErrorMessage error={error} variant="network" />;
}
Paginate the user outfits page My main inspiration for doing this is actually our potentially-huge upcoming Vercel bill lol From inspecting my Honeycomb dashboard, it looks like the main offender for backend CPU time usage is outfit images. And it looks like they come in big spikes, of lots of low usage and then suddenly 1,000 requests in one minute. My suspicion is that this is from users with many saved outfits loading their outfit page, which previously would show all of them at once. We do have `loading="lazy"` set, but not all browsers support that yet, and I've had trouble pinning down the exact behavior anyway! Anyway, paginating makes for a better experience for those huge-list users anyway. We've been meaning to do it, so here we go! My hope is that this drastically decreases backend CPU hours immediately 🤞 If not, we'll need to investigate in more detail where these outfit image requests are actually coming from! Note that I added the pagination to the existing `outfits` GraphQL endpoint, rather than creating a new one. I felt comfortable doing this because it requires login anyway, so I'm confident that other clients aren't using it; and because, while this kind of thing often creates a risk of problems with frontend and backend code getting out of sync, I think someone running old frontend code will just see only their first 30 outfits (but no pagination toolbar), and get confused and refresh the page, at which point they'll see all of them. (And I actually _prefer_ that slightly confusing UX, to avoid getting more giant spikes of outfit image requests, lol :p)
2021-11-01 19:33:40 -07:00
const outfits = data?.currentUser?.outfits || [];
return (
Paginate the user outfits page My main inspiration for doing this is actually our potentially-huge upcoming Vercel bill lol From inspecting my Honeycomb dashboard, it looks like the main offender for backend CPU time usage is outfit images. And it looks like they come in big spikes, of lots of low usage and then suddenly 1,000 requests in one minute. My suspicion is that this is from users with many saved outfits loading their outfit page, which previously would show all of them at once. We do have `loading="lazy"` set, but not all browsers support that yet, and I've had trouble pinning down the exact behavior anyway! Anyway, paginating makes for a better experience for those huge-list users anyway. We've been meaning to do it, so here we go! My hope is that this drastically decreases backend CPU hours immediately 🤞 If not, we'll need to investigate in more detail where these outfit image requests are actually coming from! Note that I added the pagination to the existing `outfits` GraphQL endpoint, rather than creating a new one. I felt comfortable doing this because it requires login anyway, so I'm confident that other clients aren't using it; and because, while this kind of thing often creates a risk of problems with frontend and backend code getting out of sync, I think someone running old frontend code will just see only their first 30 outfits (but no pagination toolbar), and get confused and refresh the page, at which point they'll see all of them. (And I actually _prefer_ that slightly confusing UX, to avoid getting more giant spikes of outfit image requests, lol :p)
2021-11-01 19:33:40 -07:00
<Box>
<PaginationToolbar
isLoading={isLoading}
totalCount={numTotalOutfits}
numPerPage={PER_PAGE}
Paginate the user outfits page My main inspiration for doing this is actually our potentially-huge upcoming Vercel bill lol From inspecting my Honeycomb dashboard, it looks like the main offender for backend CPU time usage is outfit images. And it looks like they come in big spikes, of lots of low usage and then suddenly 1,000 requests in one minute. My suspicion is that this is from users with many saved outfits loading their outfit page, which previously would show all of them at once. We do have `loading="lazy"` set, but not all browsers support that yet, and I've had trouble pinning down the exact behavior anyway! Anyway, paginating makes for a better experience for those huge-list users anyway. We've been meaning to do it, so here we go! My hope is that this drastically decreases backend CPU hours immediately 🤞 If not, we'll need to investigate in more detail where these outfit image requests are actually coming from! Note that I added the pagination to the existing `outfits` GraphQL endpoint, rather than creating a new one. I felt comfortable doing this because it requires login anyway, so I'm confident that other clients aren't using it; and because, while this kind of thing often creates a risk of problems with frontend and backend code getting out of sync, I think someone running old frontend code will just see only their first 30 outfits (but no pagination toolbar), and get confused and refresh the page, at which point they'll see all of them. (And I actually _prefer_ that slightly confusing UX, to avoid getting more giant spikes of outfit image requests, lol :p)
2021-11-01 19:33:40 -07:00
/>
<Box height="6" />
{isLoading ? (
<Center>
<HangerSpinner />
</Center>
) : !isLoggedIn ? (
<Box textAlign="center">
You can see your list of saved outfits here, once you create an
account and log in!
</Box>
Paginate the user outfits page My main inspiration for doing this is actually our potentially-huge upcoming Vercel bill lol From inspecting my Honeycomb dashboard, it looks like the main offender for backend CPU time usage is outfit images. And it looks like they come in big spikes, of lots of low usage and then suddenly 1,000 requests in one minute. My suspicion is that this is from users with many saved outfits loading their outfit page, which previously would show all of them at once. We do have `loading="lazy"` set, but not all browsers support that yet, and I've had trouble pinning down the exact behavior anyway! Anyway, paginating makes for a better experience for those huge-list users anyway. We've been meaning to do it, so here we go! My hope is that this drastically decreases backend CPU hours immediately 🤞 If not, we'll need to investigate in more detail where these outfit image requests are actually coming from! Note that I added the pagination to the existing `outfits` GraphQL endpoint, rather than creating a new one. I felt comfortable doing this because it requires login anyway, so I'm confident that other clients aren't using it; and because, while this kind of thing often creates a risk of problems with frontend and backend code getting out of sync, I think someone running old frontend code will just see only their first 30 outfits (but no pagination toolbar), and get confused and refresh the page, at which point they'll see all of them. (And I actually _prefer_ that slightly confusing UX, to avoid getting more giant spikes of outfit image requests, lol :p)
2021-11-01 19:33:40 -07:00
) : outfits.length === 0 ? (
<Box textAlign="center">
You don't have any outfits yet. Maybe you can create some!
</Box>
Paginate the user outfits page My main inspiration for doing this is actually our potentially-huge upcoming Vercel bill lol From inspecting my Honeycomb dashboard, it looks like the main offender for backend CPU time usage is outfit images. And it looks like they come in big spikes, of lots of low usage and then suddenly 1,000 requests in one minute. My suspicion is that this is from users with many saved outfits loading their outfit page, which previously would show all of them at once. We do have `loading="lazy"` set, but not all browsers support that yet, and I've had trouble pinning down the exact behavior anyway! Anyway, paginating makes for a better experience for those huge-list users anyway. We've been meaning to do it, so here we go! My hope is that this drastically decreases backend CPU hours immediately 🤞 If not, we'll need to investigate in more detail where these outfit image requests are actually coming from! Note that I added the pagination to the existing `outfits` GraphQL endpoint, rather than creating a new one. I felt comfortable doing this because it requires login anyway, so I'm confident that other clients aren't using it; and because, while this kind of thing often creates a risk of problems with frontend and backend code getting out of sync, I think someone running old frontend code will just see only their first 30 outfits (but no pagination toolbar), and get confused and refresh the page, at which point they'll see all of them. (And I actually _prefer_ that slightly confusing UX, to avoid getting more giant spikes of outfit image requests, lol :p)
2021-11-01 19:33:40 -07:00
) : (
<Wrap spacing="4" justify="space-around">
{outfits.map((outfit) => (
<WrapItem key={outfit.id}>
<OutfitCard outfit={outfit} />
</WrapItem>
))}
</Wrap>
)}
<Box height="6" />
<PaginationToolbar
isLoading={isLoading}
totalCount={numTotalOutfits}
numPerPage={PER_PAGE}
Paginate the user outfits page My main inspiration for doing this is actually our potentially-huge upcoming Vercel bill lol From inspecting my Honeycomb dashboard, it looks like the main offender for backend CPU time usage is outfit images. And it looks like they come in big spikes, of lots of low usage and then suddenly 1,000 requests in one minute. My suspicion is that this is from users with many saved outfits loading their outfit page, which previously would show all of them at once. We do have `loading="lazy"` set, but not all browsers support that yet, and I've had trouble pinning down the exact behavior anyway! Anyway, paginating makes for a better experience for those huge-list users anyway. We've been meaning to do it, so here we go! My hope is that this drastically decreases backend CPU hours immediately 🤞 If not, we'll need to investigate in more detail where these outfit image requests are actually coming from! Note that I added the pagination to the existing `outfits` GraphQL endpoint, rather than creating a new one. I felt comfortable doing this because it requires login anyway, so I'm confident that other clients aren't using it; and because, while this kind of thing often creates a risk of problems with frontend and backend code getting out of sync, I think someone running old frontend code will just see only their first 30 outfits (but no pagination toolbar), and get confused and refresh the page, at which point they'll see all of them. (And I actually _prefer_ that slightly confusing UX, to avoid getting more giant spikes of outfit image requests, lol :p)
2021-11-01 19:33:40 -07:00
/>
</Box>
);
}
export function OutfitCard({ outfit, caption = null, alt = null }) {
const image = (
2021-01-08 00:35:56 -08:00
<ClassNames>
{({ css }) => (
<OutfitThumbnail
outfitId={outfit.id}
updatedAt={outfit.updatedAt}
alt={alt ?? buildOutfitAltText(outfit)}
2021-01-08 00:35:56 -08:00
// 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 (
<Link href={`/outfits/${outfit.id}`} passHref>
<Box
as="a"
display="block"
transition="all 0.2s"
_hover={{ transform: `scale(1.05)` }}
_focus={{
transform: `scale(1.05)`,
boxShadow: "outline",
outline: "none",
}}
>
<OutfitCardLayout image={image} caption={caption ?? outfit.name} />
</Box>
</Link>
);
}
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;