From 1b10c88c518f0a8173a078f163142157eadcd000 Mon Sep 17 00:00:00 2001 From: Ian Lapham Date: Fri, 14 Jan 2022 11:33:31 -0700 Subject: [PATCH] feat: add survey popup for survey monkey (#3116) * add survey popup for survey monkey * update useEffect and add 24 hour window * upate % logic * update logic to show after duration * update timestamp conditional * small changes --- src/assets/images/survey-orb.svg | 9 +++ src/components/Popups/SurveyPopup.tsx | 106 ++++++++++++++++++++++++++ src/components/Popups/index.tsx | 10 ++- src/state/user/actions.ts | 1 + src/state/user/hooks.tsx | 13 ++++ src/state/user/reducer.ts | 8 ++ 6 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 src/assets/images/survey-orb.svg create mode 100644 src/components/Popups/SurveyPopup.tsx diff --git a/src/assets/images/survey-orb.svg b/src/assets/images/survey-orb.svg new file mode 100644 index 0000000000..953202dc6a --- /dev/null +++ b/src/assets/images/survey-orb.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/components/Popups/SurveyPopup.tsx b/src/components/Popups/SurveyPopup.tsx new file mode 100644 index 0000000000..1b24a9e031 --- /dev/null +++ b/src/components/Popups/SurveyPopup.tsx @@ -0,0 +1,106 @@ +import { Trans } from '@lingui/macro' +import { AutoColumn } from 'components/Column' +import { RowFixed } from 'components/Row' +import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp' +import { useEffect } from 'react' +import { MessageCircle, X } from 'react-feather' +import ReactGA from 'react-ga' +import { useShowSurveyPopup } from 'state/user/hooks' +import styled from 'styled-components/macro' +import { ExternalLink, ThemedText, Z_INDEX } from 'theme' + +import BGImage from '../../assets/images/survey-orb.svg' +import useTheme from '../../hooks/useTheme' + +const Wrapper = styled(AutoColumn)` + background: #edeef2; + position: relative; + border-radius: 12px; + padding: 18px; + max-width: 360px; + box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); + color: ${({ theme }) => theme.text1}; + overflow: hidden; + + ${({ theme }) => theme.mediaWidth.upToSmall` + max-width: 100%; + `} +` + +const BGOrb = styled.img` + position: absolute; + right: -64px; + top: -64px; + width: 180px; + z-index: ${Z_INDEX.sticky}; +` + +const WrappedCloseIcon = styled(X)` + position: absolute; + top: 10px; + right: 10px; + width: 20px; + height: 20px; + stroke: #7c7c80; + z-index: ${Z_INDEX.fixed}; + :hover { + cursor: pointer; + opacity: 0.8; + } +` + +const END_TIMESTAMP = 1642215971 // Jan 15th + +export default function SurveyPopup() { + const theme = useTheme() + const [showPopup, setShowSurveyPopup] = useShowSurveyPopup() + + // show popup to 1% of users + useEffect(() => { + // has not visited page during A/B testing if undefined + if (showPopup === undefined) { + if (Math.random() < 0.01) { + setShowSurveyPopup(true) + // log a case of succesful view + ReactGA.event({ + category: 'Survey', + action: 'Saw Survey', + }) + } + } + }, [setShowSurveyPopup, showPopup]) + + // limit survey to 24 hours based on timestamps + const timestamp = useCurrentBlockTimestamp() + const durationOver = timestamp ? timestamp.toNumber() > END_TIMESTAMP : false + + return ( + <> + {!showPopup || durationOver ? null : ( + + { + ReactGA.event({ + category: 'Survey', + action: 'Clicked Survey Link', + }) + setShowSurveyPopup(false) + }} + /> + + + + + + Tell us what you think ↗ + + + + + Take a 10 minute survey to help us improve your experience in the Uniswap app. + + + )} + + ) +} diff --git a/src/components/Popups/index.tsx b/src/components/Popups/index.tsx index 6ff29f8956..ae1cf23bcb 100644 --- a/src/components/Popups/index.tsx +++ b/src/components/Popups/index.tsx @@ -4,10 +4,11 @@ import styled from 'styled-components/macro' import { MEDIA_WIDTHS } from 'theme' import { useActivePopups } from '../../state/application/hooks' -import { useURLWarningVisible } from '../../state/user/hooks' +import { useShowSurveyPopup, useURLWarningVisible } from '../../state/user/hooks' import { AutoColumn } from '../Column' import ClaimPopup from './ClaimPopup' import PopupItem from './PopupItem' +import SurveyPopup from './SurveyPopup' const MobilePopupWrapper = styled.div<{ height: string | number }>` position: relative; @@ -59,6 +60,9 @@ export default function Popups() { // get all popups const activePopups = useActivePopups() + // show survey popup if user has not closed + const [showSurveyPopup] = useShowSurveyPopup() + const urlWarningActive = useURLWarningVisible() // need extra padding if network is not L1 Ethereum @@ -69,12 +73,14 @@ export default function Popups() { <> + {activePopups.map((item) => ( ))} - 0 ? 'fit-content' : 0}> + 0 || showSurveyPopup ? 'fit-content' : 0}> + {activePopups // reverse so new items up front .slice(0) .reverse() diff --git a/src/state/user/actions.ts b/src/state/user/actions.ts index d5c035e52f..9296f38327 100644 --- a/src/state/user/actions.ts +++ b/src/state/user/actions.ts @@ -18,6 +18,7 @@ export const updateMatchesDarkMode = createAction<{ matchesDarkMode: boolean }>( export const updateUserDarkMode = createAction<{ userDarkMode: boolean }>('user/updateUserDarkMode') export const updateUserExpertMode = createAction<{ userExpertMode: boolean }>('user/updateUserExpertMode') export const updateUserLocale = createAction<{ userLocale: SupportedLocale }>('user/updateUserLocale') +export const updateShowSurveyPopup = createAction<{ showSurveyPopup: boolean }>('user/updateShowSurveyPopup') export const updateUserClientSideRouter = createAction<{ userClientSideRouter: boolean }>( 'user/updateUserClientSideRouter' ) diff --git a/src/state/user/hooks.tsx b/src/state/user/hooks.tsx index dc0af85695..278cf0533b 100644 --- a/src/state/user/hooks.tsx +++ b/src/state/user/hooks.tsx @@ -20,6 +20,7 @@ import { SerializedPair, SerializedToken, updateHideClosedPositions, + updateShowSurveyPopup, updateUserClientSideRouter, updateUserDarkMode, updateUserDeadline, @@ -104,6 +105,18 @@ export function useExpertModeManager(): [boolean, () => void] { return [expertMode, toggleSetExpertMode] } +export function useShowSurveyPopup(): [boolean | undefined, (showPopup: boolean) => void] { + const dispatch = useAppDispatch() + const showSurveyPopup = useAppSelector((state) => state.user.showSurveyPopup) + const toggleShowSurveyPopup = useCallback( + (showPopup: boolean) => { + dispatch(updateShowSurveyPopup({ showSurveyPopup: showPopup })) + }, + [dispatch] + ) + return [showSurveyPopup, toggleShowSurveyPopup] +} + export function useClientSideRouter(): [boolean, (userClientSideRouter: boolean) => void] { const dispatch = useAppDispatch() diff --git a/src/state/user/reducer.ts b/src/state/user/reducer.ts index 9425e94abe..57cd35c57c 100644 --- a/src/state/user/reducer.ts +++ b/src/state/user/reducer.ts @@ -12,6 +12,7 @@ import { SerializedToken, updateHideClosedPositions, updateMatchesDarkMode, + updateShowSurveyPopup, updateUserClientSideRouter, updateUserDarkMode, updateUserDeadline, @@ -60,6 +61,9 @@ export interface UserState { timestamp: number URLWarningVisible: boolean + + // undefined means has not gone through A/B split yet + showSurveyPopup: boolean | undefined } function pairKey(token0Address: string, token1Address: string) { @@ -80,6 +84,7 @@ export const initialState: UserState = { pairs: {}, timestamp: currentTimestamp(), URLWarningVisible: true, + showSurveyPopup: undefined, } export default createReducer(initialState, (builder) => @@ -147,6 +152,9 @@ export default createReducer(initialState, (builder) => .addCase(updateHideClosedPositions, (state, action) => { state.userHideClosedPositions = action.payload.userHideClosedPositions }) + .addCase(updateShowSurveyPopup, (state, action) => { + state.showSurveyPopup = action.payload.showSurveyPopup + }) .addCase(addSerializedToken, (state, { payload: { serializedToken } }) => { if (!state.tokens) { state.tokens = {}