import React from "react"; import { css } from "@emotion/react"; import { AspectRatio, Box, Button, Center, Flex, FormControl, FormErrorMessage, FormLabel, Grid, Input, InputGroup, InputRightElement, Popover, PopoverArrow, PopoverBody, PopoverContent, PopoverTrigger, Spinner, Tab, TabList, TabPanel, TabPanels, Tabs, Textarea, useClipboard, useColorModeValue, VStack, } from "@chakra-ui/react"; import { Delay, ErrorMessage, Heading1, Heading2, usePageTitle } from "./util"; import { gql, useQuery } from "@apollo/client"; import { CheckIcon, WarningIcon } from "@chakra-ui/icons"; function OutfitUrlsPage() { usePageTitle("Changing our outfit URLs"); return ( <> Changing our outfit URLs

Hi, friends! Sorry for the trouble ๐Ÿ˜“ In short, by switching to the new outfit URLs below, we'll decrease our hosting costs by $20/month! ๐Ÿ™

The history

When we started hosting outfit images back in 2012, we didn't know a lot about web infrastructure, and we weren't thinking a lot about permanent URLs ๐Ÿ˜… We uploaded images directly to{" "} Amazon S3, and gave you Amazon's URL for them, at amazonaws.com.

Since then, we've grown a lot, and our Amazon costs have increased a lot too! These days, it costs about $30/month to serve outfit images from S3โ€”and $20 of that is just to store our millions of outfit images, including the ones nobody visits ๐Ÿ˜…

So, we've moved our apps to a new, more cost-efficient way to share outfit images! But, until we delete the old images from Amazon S3 altogether, we're still paying $20/month just to support the old amazonaws.com URLs.

I looked hard for a way to redirect the old Amazon URLs to our new service, but it seems to not be possible, and it seems like $20/month could be better spent another way ๐Ÿ˜–

We haven't removed these images from Amazon yet, so old image URLs will continue to work until at least July 1, 2021! Then, we'll replace the old images with a short message and a link to this page, so it's easy to learn what happened and fix things up.

I'm truly sorry for breaking some of the lookups and petpages out there, and I hope this tool helps folks migrate to the new version quickly and easily! ๐Ÿ™

Thanks again everyone for your constant support, we appreciate you so so much!! ๐Ÿ’– And please let me know at{" "} matchu@openneo.net with any thoughts you haveโ€”it's always great to hear from you, and it always make things better ๐Ÿ’•

); } function OutfitUrlConverter() { return ( Convert an image Convert a lookup/petpage ); } function SingleImageConverter() { const [inputUrl, setInputUrl] = React.useState(""); let outputUrl; let parseError; try { outputUrl = convertS3OutfitUrl(inputUrl); } catch (e) { parseError = e; } const previewBackground = useColorModeValue("gray.200", "whiteAlpha.300"); const { onCopy, hasCopied } = useClipboard(outputUrl); return ( Enter an outfit image URL setInputUrl(e.target.value)} /> {parseError?.message || null} Then, use this new URL in your layouts instead: {outputUrl && ( )}
{outputUrl && ( )}
); } function BulkImageConverter() { const [inputHtml, setInputHtml] = React.useState(""); const parsedUrls = parseManyS3OutfitUrlsFromHtml(inputHtml); const outfitIds = parsedUrls.map((pu) => pu.outfitId); // TODO: Do this query in batches for large pages? const { loading, error, data } = useQuery( gql` query OutfitUrlsBulkImageConverter($outfitIds: [ID!]!) { outfits(ids: $outfitIds) { id # Rather than send requests for different sizes separately, I'm just # requesting them all and having the client choose, to simplify the # query. gzip should compress it very efficiently! imageUrl600: imageUrl(size: SIZE_600) imageUrl300: imageUrl(size: SIZE_300) imageUrl150: imageUrl(size: SIZE_150) } } `, { variables: { outfitIds }, skip: outfitIds.length === 0, onError: (e) => console.error(e), } ); const { outputHtml, numReplacements, replacementErrors } = React.useMemo( () => inputHtml && data?.outfits ? replaceS3OutfitUrlsInHtml(inputHtml, data.outfits) : { outputHtml: "", numReplacements: 0, replacementErrors: [] }, [inputHtml, data?.outfits] ); React.useEffect(() => { for (const replacementError of replacementErrors) { console.error("Error replacing outfit URLs in HTML:", replacementError); } }, [replacementErrors]); const { onCopy, hasCopied } = useClipboard(outputHtml); return ( Enter your lookup/petpage HTML