2020-05-10 00:21:04 -07:00
import React from "react" ;
2021-01-03 19:11:46 -08:00
import { ClassNames } from "@emotion/react" ;
2020-05-10 00:54:23 -07:00
import gql from "graphql-tag" ;
2020-08-12 00:37:31 -07:00
import {
Box ,
Button ,
Flex ,
Input ,
2020-10-07 09:48:55 -07:00
Textarea ,
2020-08-12 00:37:31 -07:00
useColorModeValue ,
useTheme ,
useToast ,
2020-10-22 16:01:54 -07:00
VStack ,
2020-12-25 09:08:33 -08:00
} from "@chakra-ui/react" ;
2020-09-06 18:12:34 -07:00
import { useHistory , useLocation } from "react-router-dom" ;
2020-07-31 23:10:34 -07:00
import { useLazyQuery } from "@apollo/client" ;
2020-05-10 00:21:04 -07:00
2021-01-04 01:17:30 -08:00
import {
Heading1 ,
useCommonStyles ,
useLocalStorage ,
usePageTitle ,
} from "./util" ;
2020-07-22 22:07:45 -07:00
import OutfitPreview from "./components/OutfitPreview" ;
2020-10-09 08:01:46 -07:00
import SpeciesColorPicker from "./components/SpeciesColorPicker" ;
2021-01-14 06:43:55 -08:00
import WIPCallout from "./components/WIPCallout" ;
2020-05-10 00:21:04 -07:00
2021-01-03 19:49:08 -08:00
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" ;
2020-05-10 00:21:04 -07:00
function HomePage ( ) {
2020-09-11 23:53:57 -07:00
usePageTitle ( null ) ;
2020-07-31 16:57:37 -07:00
useSupportSetup ( ) ;
2020-05-17 23:26:00 -07:00
2020-07-22 22:07:45 -07:00
const [ previewState , setPreviewState ] = React . useState ( null ) ;
2021-01-16 12:01:28 -08:00
React . useEffect ( ( ) => {
if ( window . location . href . includes ( "send-test-error-for-sentry" ) ) {
throw new Error ( "Test error for Sentry" ) ;
}
} ) ;
2020-05-10 00:21:04 -07:00
return (
2020-09-06 18:12:34 -07:00
< Flex direction = "column" align = "center" textAlign = "center" marginTop = "8" >
2020-05-17 23:26:00 -07:00
< Box
width = "200px"
height = "200px"
2020-07-20 21:32:42 -07:00
borderRadius = "lg"
2020-05-17 23:26:00 -07:00
boxShadow = "md"
2020-07-22 22:07:45 -07:00
overflow = "hidden"
>
< OutfitPreview
speciesId = { previewState ? . speciesId }
colorId = { previewState ? . colorId }
pose = { previewState ? . pose }
wornItemIds = { [ ] }
2020-09-13 04:12:14 -07:00
loadingDelayMs = { 1500 }
2020-07-22 22:07:45 -07:00
placeholder = {
< Box
as = "img"
src = { HomepageSplashImg }
srcSet = { ` ${ HomepageSplashImg } 1x, ${ HomepageSplashImg2x } 2x ` }
alt = ""
/ >
}
/ >
< / B o x >
2020-05-17 23:26:00 -07:00
< Box height = "4" / >
< Heading1 > Dress to Impress < / H e a d i n g 1 >
2020-10-07 07:52:34 -07:00
< Box fontSize = "lg" fontStyle = "italic" opacity = "0.85" role = "doc-subtitle" >
Design and share your Neopets outfits !
< / B o x >
2020-05-17 23:26:00 -07:00
< Box height = "8" / >
2020-07-22 22:07:45 -07:00
< StartOutfitForm onChange = { setPreviewState } / >
2020-05-17 23:26:00 -07:00
< Box height = "4" / >
< Box fontStyle = "italic" fontSize = "sm" >
or
< / B o x >
< Box height = "4" / >
< SubmitPetForm / >
2020-10-07 09:48:55 -07:00
< Box height = "16" / >
< FeedbackFormSection / >
2021-01-14 06:43:55 -08:00
< Box height = "16" / >
< WIPCallout details = "We started building this last year, but, well… what a year 😅 Anyway, this will eventually become the main site, at impress.openneo.net!" >
Maybe we ' ll rename it to Impress 2021 … or maybe not ! 🤔
< / W I P C a l l o u t >
2020-05-17 23:26:00 -07:00
< / F l e x >
2020-05-10 00:21:04 -07:00
) ;
}
2020-07-22 22:07:45 -07:00
function StartOutfitForm ( { onChange } ) {
2020-05-10 00:21:04 -07:00
const history = useHistory ( ) ;
2020-05-23 13:23:24 -07:00
const idealPose = React . useMemo (
( ) => ( Math . random ( ) > 0.5 ? "HAPPY_FEM" : "HAPPY_MASC" ) ,
[ ]
) ;
2020-05-10 00:21:04 -07:00
const [ speciesId , setSpeciesId ] = React . useState ( "1" ) ;
const [ colorId , setColorId ] = React . useState ( "8" ) ;
const [ isValid , setIsValid ] = React . useState ( true ) ;
2020-05-23 13:23:24 -07:00
const [ closestPose , setClosestPose ] = React . useState ( idealPose ) ;
2020-05-10 00:21:04 -07:00
2020-05-10 00:54:23 -07:00
const onSubmit = ( e ) => {
e . preventDefault ( ) ;
2020-05-10 00:21:04 -07:00
if ( ! isValid ) {
return ;
}
const params = new URLSearchParams ( {
species : speciesId ,
color : colorId ,
2020-05-23 13:23:24 -07:00
pose : closestPose ,
2020-05-10 00:21:04 -07:00
} ) ;
history . push ( ` /outfits/new? ${ params } ` ) ;
} ;
2020-08-12 00:37:31 -07:00
const buttonBgColor = useColorModeValue ( "green.600" , "green.300" ) ;
const buttonBgColorHover = useColorModeValue ( "green.700" , "green.200" ) ;
2020-05-10 00:21:04 -07:00
return (
< form onSubmit = { onSubmit } >
< Flex >
< SpeciesColorPicker
speciesId = { speciesId }
colorId = { colorId }
2020-05-23 13:23:24 -07:00
idealPose = { idealPose }
2020-05-10 00:21:04 -07:00
showPlaceholders
2020-09-21 02:43:58 -07:00
colorPlaceholderText = "Blue"
speciesPlaceholderText = "Acara"
2020-05-23 13:23:24 -07:00
onChange = { ( species , color , isValid , closestPose ) => {
2020-05-10 00:21:04 -07:00
setSpeciesId ( species . id ) ;
setColorId ( color . id ) ;
setIsValid ( isValid ) ;
2020-05-23 13:23:24 -07:00
setClosestPose ( closestPose ) ;
2020-07-22 22:07:45 -07:00
if ( isValid ) {
onChange ( {
speciesId : species . id ,
colorId : color . id ,
pose : closestPose ,
} ) ;
}
2020-05-10 00:21:04 -07:00
} }
/ >
< Box width = "4" / >
2020-05-18 01:21:46 -07:00
< Button
type = "submit"
2020-07-20 21:32:42 -07:00
colorScheme = "green"
2020-05-18 01:21:46 -07:00
disabled = { ! isValid }
2020-08-12 00:37:31 -07:00
backgroundColor = { buttonBgColor }
_hover = { { backgroundColor : buttonBgColorHover } }
2020-05-18 01:21:46 -07:00
>
2020-05-10 01:17:16 -07:00
Start
< / B u t t o n >
2020-05-10 00:21:04 -07:00
< / F l e x >
< / f o r m >
) ;
}
2020-05-10 00:54:23 -07:00
function SubmitPetForm ( ) {
const history = useHistory ( ) ;
const theme = useTheme ( ) ;
const toast = useToast ( ) ;
const [ petName , setPetName ] = React . useState ( "" ) ;
const [ loadPet , { loading } ] = useLazyQuery (
gql `
2020-05-19 14:48:54 -07:00
query SubmitPetForm ( $petName : String ! ) {
2020-05-10 00:54:23 -07:00
petOnNeopetsDotCom ( petName : $petName ) {
color {
id
}
species {
id
}
2020-05-23 13:55:59 -07:00
pose
2020-05-10 00:54:23 -07:00
items {
id
}
}
}
` ,
{
fetchPolicy : "network-only" ,
onCompleted : ( data ) => {
if ( ! data ) return ;
2020-05-23 13:55:59 -07:00
const { species , color , pose , items } = data . petOnNeopetsDotCom ;
2020-05-10 00:54:23 -07:00
const params = new URLSearchParams ( {
name : petName ,
species : species . id ,
color : color . id ,
2020-05-23 13:55:59 -07:00
pose ,
2020-05-10 00:54:23 -07:00
} ) ;
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 ( ) ;
2020-05-18 00:20:48 -07:00
2020-05-10 00:54:23 -07:00
loadPet ( { variables : { petName } } ) ;
2020-05-18 00:20:48 -07:00
// Start preloading the WardrobePage, too!
// eslint-disable-next-line no-unused-expressions
import ( "./WardrobePage" ) ;
2020-05-10 00:54:23 -07:00
} ;
2021-01-04 01:17:30 -08:00
const { brightBackground } = useCommonStyles ( ) ;
2020-08-12 00:37:31 -07:00
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" ) ;
2020-05-10 00:54:23 -07:00
return (
2021-01-03 19:11:46 -08:00
< ClassNames >
{ ( { css } ) => (
< form onSubmit = { onSubmit } >
< Flex >
< Input
value = { petName }
onChange = { ( e ) => setPetName ( e . target . value ) }
isDisabled = { loading }
placeholder = "Enter a pet's name"
aria - label = "Enter a pet's name"
borderColor = { inputBorderColor }
_hover = { { borderColor : inputBorderColorHover } }
2021-01-04 01:17:30 -08:00
background = { brightBackground }
2021-01-03 19:11:46 -08:00
boxShadow = "md"
width = "14em"
className = { css `
& : : placeholder {
color : $ { theme . colors . gray [ "500" ] } ;
}
` }
/ >
< Box width = "4" / >
< Button
type = "submit"
colorScheme = "green"
isDisabled = { ! petName }
isLoading = { loading }
backgroundColor = { buttonBgColor } // for AA contrast
_hover = { { backgroundColor : buttonBgColorHover } }
>
Start
< / B u t t o n >
< / F l e x >
< / f o r m >
) }
< / C l a s s N a m e s >
2020-05-10 00:54:23 -07:00
) ;
}
2020-10-07 09:48:55 -07:00
function FeedbackFormSection ( ) {
2021-01-04 01:17:30 -08:00
const { brightBackground } = useCommonStyles ( ) ;
2020-10-22 16:01:54 -07:00
const pitchBorderColor = useColorModeValue ( "gray.300" , "green.400" ) ;
const formBorderColor = useColorModeValue ( "gray.300" , "blue.400" ) ;
2020-10-07 09:48:55 -07:00
return (
2020-10-22 16:01:54 -07:00
< VStack spacing = "4" alignItems = "stretch" >
2021-01-04 01:17:30 -08:00
< FeedbackFormContainer
background = { brightBackground }
borderColor = { pitchBorderColor }
>
2020-10-22 16:01:54 -07:00
< Flex position = "relative" alignItems = "center" >
< Box padding = "2" borderRadius = "lg" overflow = "hidden" flex = "0 0 auto" >
< Box
as = "img"
src = { FeedbackXweeImg }
srcSet = { ` ${ FeedbackXweeImg } 1x, ${ FeedbackXweeImg2x } 2x ` }
height = "90px"
width = "90px"
opacity = "0.9"
alt = ""
/ >
< / B o x >
< FeedbackFormPitch / >
< / F l e x >
< / F e e d b a c k F o r m C o n t a i n e r >
< FeedbackFormContainer
borderColor = { formBorderColor }
image = {
< Box
as = "img"
src = { FeedbackXweeImg }
srcSet = { ` ${ FeedbackXweeImg } 1x, ${ FeedbackXweeImg2x } 2x ` }
height = "90px"
width = "90px"
opacity = "0.9"
alt = ""
/ >
}
>
< FeedbackForm / >
< / F e e d b a c k F o r m C o n t a i n e r >
< / V S t a c k >
) ;
}
2021-01-04 01:17:30 -08:00
function FeedbackFormContainer ( { background , borderColor , children } ) {
2020-10-22 16:01:54 -07:00
return (
< Box
2020-10-07 09:48:55 -07:00
as = "section"
2021-01-04 01:17:30 -08:00
background = { background }
2020-10-07 09:48:55 -07:00
border = "1px solid"
borderColor = { borderColor }
borderRadius = "lg"
boxShadow = "lg"
maxWidth = "500px"
paddingLeft = "2"
paddingRight = "4"
paddingY = "2"
2021-01-04 01:17:30 -08:00
transition = "all 0.2s"
2020-10-07 09:48:55 -07:00
>
2020-10-22 16:01:54 -07:00
{ children }
< / B o x >
2020-10-07 09:48:55 -07:00
) ;
}
2020-10-22 16:01:54 -07:00
function FeedbackFormPitch ( ) {
2020-10-07 09:48:55 -07:00
return (
< Flex direction = "column" textAlign = "left" opacity = "0.9" >
< Box as = "header" > Hi friends ! Welcome to the beta ! < / B o x >
< Box as = "p" fontSize = "sm" >
This is the new Dress to Impress ! It ' s ready for the future , and it even
works great on mobile ! More coming soon !
< / B o x >
2020-10-22 16:01:54 -07:00
< Box fontSize = "sm" marginTop = "1" >
↓ Got ideas ? Send them to us , please ! { " " }
< span role = "img" aria - label = "Sparkle heart emoji" >
💖
< / s p a n >
2020-10-07 09:48:55 -07:00
< / B o x >
< / F l e x >
) ;
}
2020-10-22 16:01:54 -07:00
function FeedbackForm ( ) {
2020-10-07 09:48:55 -07:00
const [ content , setContent ] = React . useState ( "" ) ;
2020-10-09 08:18:18 -07:00
const [ email , setEmail ] = useLocalStorage ( "DTIFeedbackFormEmail" , "" ) ;
const [ isSending , setIsSending ] = React . useState ( false ) ;
2020-10-07 09:48:55 -07:00
const toast = useToast ( ) ;
2020-10-09 08:18:18 -07:00
const onSubmit = React . useCallback (
( e ) => {
e . preventDefault ( ) ;
fetch ( "/api/sendFeedback" , {
method : "POST" ,
2020-10-09 08:55:03 -07:00
headers : { "Content-Type" : "application/json" } ,
body : JSON . stringify ( { content , email } ) ,
2020-10-09 08:18:18 -07:00
} )
. 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 ) ;
} ,
2020-10-22 16:01:54 -07:00
[ content , email , toast ]
2020-10-09 08:18:18 -07:00
) ;
2021-01-04 01:17:30 -08:00
const { brightBackground } = useCommonStyles ( ) ;
2020-10-07 09:48:55 -07:00
return (
< Box
as = "form"
// We use Grid here rather than our usual Flex, mainly so the fields will
// tab in the correct order!
display = "grid"
gridTemplateAreas = { ` "email send" "content content" ` }
gridTemplateColumns = "1fr auto"
gridGap = "2"
2020-10-09 08:18:18 -07:00
onSubmit = { onSubmit }
2020-10-07 09:48:55 -07:00
>
< Input
type = "email"
placeholder = "Email address (optional)"
size = "sm"
gridArea = "email"
2020-10-09 08:18:18 -07:00
value = { email }
onChange = { ( e ) => setEmail ( e . target . value ) }
2021-01-04 01:17:30 -08:00
background = { brightBackground }
2020-10-07 09:48:55 -07:00
/ >
< Textarea
size = "sm"
placeholder = { "I love…\nI wish…\nNext, you should add…" }
gridArea = "content"
value = { content }
onChange = { ( e ) => setContent ( e . target . value ) }
2021-01-04 01:17:30 -08:00
background = { brightBackground }
2020-10-07 09:48:55 -07:00
/ >
< Button
type = "submit"
size = "sm"
colorScheme = "blue"
gridArea = "send"
2020-10-22 16:01:54 -07:00
isDisabled = { content . trim ( ) . length === 0 }
2020-10-09 08:18:18 -07:00
isLoading = { isSending }
2020-10-07 09:48:55 -07:00
>
Send
< / B u t t o n >
< / B o x >
) ;
}
2020-07-31 16:57:37 -07:00
/ * *
* useSupportSetup helps our support staff get set up with special access .
* If you provide ? supportSecret = ... in the URL , we ' ll save it in a cookie and
* pop up a toast !
*
* This doesn 't guarantee the secret is correct, of course! We don' t bother to
* check that here ; the server will reject requests from bad support secrets .
* And there 's nothing especially secret in the support UI, so it' s okay if
* other people know about the tools and poke around a powerless interface !
* /
function useSupportSetup ( ) {
const location = useLocation ( ) ;
const toast = useToast ( ) ;
React . useEffect ( ( ) => {
const params = new URLSearchParams ( location . search ) ;
const supportSecret = params . get ( "supportSecret" ) ;
2020-08-01 00:42:36 -07:00
const existingSupportSecret = localStorage . getItem ( "supportSecret" ) ;
2020-07-31 16:57:37 -07:00
2020-08-01 00:42:36 -07:00
if ( supportSecret && supportSecret !== existingSupportSecret ) {
2020-07-31 16:57:37 -07:00
localStorage . setItem ( "supportSecret" , supportSecret ) ;
toast ( {
title : "Support secret saved!" ,
description :
` You should now see special Support UI across the site. ` +
` Thanks for your help! 💖 ` ,
status : "success" ,
duration : 10000 ,
isClosable : true ,
} ) ;
} else if ( supportSecret === "" ) {
localStorage . removeItem ( "supportSecret" ) ;
toast ( {
title : "Support secret deleted." ,
description : ` The Support UI will now stop appearing on this device. ` ,
status : "success" ,
duration : 10000 ,
isClosable : true ,
} ) ;
}
} , [ location , toast ] ) ;
}
2020-05-10 00:21:04 -07:00
export default HomePage ;