import React from "react"; import { ClassNames } from "@emotion/react"; import gql from "graphql-tag"; import { Box, Button, Flex, HStack, IconButton, Input, InputGroup, InputLeftElement, InputRightElement, 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"); }; 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 Latest items & search! Your Outfits page! Coming soon Search by items you own 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} />