import React from "react"; import { ClassNames } from "@emotion/react"; import gql from "graphql-tag"; import { Box, Button, Flex, HStack, IconButton, Input, InputGroup, InputLeftElement, InputRightElement, Link, ListItem, Textarea, UnorderedList, useColorModeValue, useTheme, useToast, VStack, } from "@chakra-ui/react"; import { ArrowForwardIcon, SearchIcon } from "@chakra-ui/icons"; import { useHistory, useLocation } from "react-router-dom"; import { useLazyQuery, useQuery } from "@apollo/client"; import { Delay, ErrorMessage, Heading1, Heading2, useCommonStyles, useLocalStorage, usePageTitle, } from "./util"; import OutfitPreview from "./components/OutfitPreview"; import SpeciesColorPicker from "./components/SpeciesColorPicker"; import SquareItemCard, { SquareItemCardSkeleton, } from "./components/SquareItemCard"; import WIPCallout from "./components/WIPCallout"; import HomepageSplashImg from "./images/homepage-splash.png"; import HomepageSplashImg2x from "./images/homepage-splash@2x.png"; import FeedbackXweeImg from "./images/feedback-xwee.png"; import FeedbackXweeImg2x from "./images/feedback-xwee@2x.png"; function HomePage() { usePageTitle(null); useSupportSetup(); const [previewState, setPreviewState] = React.useState(null); React.useEffect(() => { if (window.location.href.includes("send-test-error-for-sentry")) { throw new Error("Test error for Sentry"); } }); return ( } /> Dress to Impress Design and share your Neopets outfits! or Maybe we'll rename it to Impress 2021… or maybe not! 🤔 ); } function StartOutfitForm({ onChange }) { const history = useHistory(); const idealPose = React.useMemo( () => (Math.random() > 0.5 ? "HAPPY_FEM" : "HAPPY_MASC"), [] ); const [speciesId, setSpeciesId] = React.useState("1"); const [colorId, setColorId] = React.useState("8"); const [isValid, setIsValid] = React.useState(true); const [closestPose, setClosestPose] = React.useState(idealPose); const onSubmit = (e) => { e.preventDefault(); if (!isValid) { return; } const params = new URLSearchParams({ species: speciesId, color: colorId, pose: closestPose, }); history.push(`/outfits/new?${params}`); }; const buttonBgColor = useColorModeValue("green.600", "green.300"); const buttonBgColorHover = useColorModeValue("green.700", "green.200"); return (
{ setSpeciesId(species.id); setColorId(color.id); setIsValid(isValid); setClosestPose(closestPose); if (isValid) { onChange({ speciesId: species.id, colorId: color.id, pose: closestPose, }); } }} />
); } function SubmitPetForm() { const history = useHistory(); const theme = useTheme(); const toast = useToast(); const [petName, setPetName] = React.useState(""); const [loadPet, { loading }] = useLazyQuery( gql` query SubmitPetForm($petName: String!) { petOnNeopetsDotCom(petName: $petName) { color { id } species { id } pose items { id } } } `, { fetchPolicy: "network-only", onCompleted: (data) => { if (!data) return; const { species, color, pose, items } = data.petOnNeopetsDotCom; const params = new URLSearchParams({ name: petName, species: species.id, color: color.id, pose, }); for (const item of items) { params.append("objects[]", item.id); } history.push(`/outfits/new?${params}`); }, onError: () => { toast({ title: "We couldn't load that pet, sorry 😓", description: "Is it spelled correctly?", status: "error", }); }, } ); const onSubmit = (e) => { e.preventDefault(); loadPet({ variables: { petName } }); // Start preloading the WardrobePage, too! // eslint-disable-next-line no-unused-expressions import("./WardrobePage").catch((e) => { // Let's just let this slide, because it's a preload error. Critical // failures will happen elsewhere, and trigger reloads! console.error(e); }); }; const { brightBackground } = useCommonStyles(); const inputBorderColor = useColorModeValue("green.600", "green.500"); const inputBorderColorHover = useColorModeValue("green.400", "green.300"); const buttonBgColor = useColorModeValue("green.600", "green.300"); const buttonBgColorHover = useColorModeValue("green.700", "green.200"); return ( {({ css }) => (
setPetName(e.target.value)} isDisabled={loading} placeholder="Enter a pet's name" aria-label="Enter a pet's name" borderColor={inputBorderColor} _hover={{ borderColor: inputBorderColorHover }} background={brightBackground} boxShadow="md" width="14em" className={css` &::placeholder { color: ${theme.colors.gray["500"]}; } `} />
)}
); } function NewItemsSection() { return ( Latest items ); } function ItemsSearchField() { const [query, setQuery] = React.useState(""); const { brightBackground } = useCommonStyles(); const history = useHistory(); return (
{ if (query) { history.push(`/items/search/${encodeURIComponent(query)}`); } }} > setQuery(e.target.value)} placeholder="Search all items…" borderRadius="full" /> } aria-label="Search" minWidth="1.5rem" minHeight="1.5rem" width="1.5rem" height="1.5rem" borderRadius="full" opacity={query ? 1 : 0} transition="opacity 0.2s" aria-hidden={query ? "false" : "true"} />
); } function NewItemsSectionContent() { const { loading, error, data } = useQuery( gql` query NewItemsSection { newestItems { id name thumbnailUrl } } ` ); if (loading) { return ( ); } if (error) { return ( Couldn't load new items. Check your connection and try again! ); } return ( {data.newestItems.map((item) => ( ))} ); } function ItemCardHStack({ children }) { return ( // HACK: I wanted to just have an HStack with overflow:auto and internal // paddingX, but the right-hand-side padding didn't seem to work // during overflow. This was the best I could come up with... {children} ); } function FeedbackFormSection() { const { brightBackground } = useCommonStyles(); const pitchBorderColor = useColorModeValue("gray.300", "green.400"); const formBorderColor = useColorModeValue("gray.300", "blue.400"); return ( } > ); } function FeedbackFormContainer({ background, borderColor, children }) { return ( {children} ); } function FeedbackFormPitch() { return ( Hi friends! Welcome to the beta! This is the new Dress to Impress! It's ready for the future, and it even works great on mobile! More coming soon! New updates (Mar 16) Support new HTML5 items Lots of old item bug fixes! Focused on reliability 💛 See more on Twitter! Coming soon Outfit saving Better item list pages …a lot more things{" "} 😅 ↓ Got ideas? Send them to us, please!{" "} 💖 ); } function FeedbackForm() { const [content, setContent] = React.useState(""); const [email, setEmail] = useLocalStorage("DTIFeedbackFormEmail", ""); const [isSending, setIsSending] = React.useState(false); const toast = useToast(); const onSubmit = React.useCallback( (e) => { e.preventDefault(); fetch("/api/sendFeedback", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ content, email }), }) .then((res) => { if (!res.ok) { throw new Error(`/api/sendFeedback returned status ${res.status}`); } setIsSending(false); setContent(""); toast({ status: "success", title: "Got it! We'll take a look soon.", description: "Thanks for helping us get better! Best wishes to you and your " + "pets!!", }); }) .catch((e) => { setIsSending(false); console.error(e); toast({ status: "warning", title: "Oops, we had an error sending this, sorry!", description: "We'd still love to hear from you! Please reach out to " + "matchu@openneo.net with whatever's on your mind. Thanks and " + "enjoy the site!", duration: null, isClosable: true, }); }); setIsSending(true); }, [content, email, toast] ); const { brightBackground } = useCommonStyles(); return ( setEmail(e.target.value)} background={brightBackground} />