Skip to content

Commit

Permalink
Mobile App Upsell on Landing Page (#2324)
Browse files Browse the repository at this point in the history
  • Loading branch information
jakzaizzat authored Feb 20, 2024
1 parent 0445c61 commit 0b631f5
Show file tree
Hide file tree
Showing 6 changed files with 551 additions and 4 deletions.
1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
"ts-node": "^10.9.1",
"utf-8-validate": "^5.0.7",
"uuid": "9.0.0-beta.0",
"vaul": "^0.9.0",
"viem": "^1.2.12",
"wagmi": "^1.3.10",
"web3-utils": "^4.1.0"
Expand Down
9 changes: 6 additions & 3 deletions apps/web/src/contexts/AppProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import MaintenanceStatusProvider from '~/shared/contexts/MaintenanceStatusContex
import isProduction from '~/utils/isProduction';

import AnalyticsProvider from './analytics/WebAnalyticsProvider';
import BottomSheetProvider from './bottomsheet/BottomSheetContext';
import Boundary from './boundary/Boundary';
import { WebErrorReportingProvider } from './errorReporting/WebErrorReportingProvider';
import GlobalLayoutContextProvider from './globalLayout/GlobalLayoutContext';
Expand Down Expand Up @@ -64,9 +65,11 @@ export default function AppProvider({
<GlobalLayoutContextProvider
preloadedQuery={globalLayoutContextPreloadedQuery}
>
<FullPageNftDetailModalListener />
{isProd ? null : <Debugger />}
{children}
<BottomSheetProvider>
<FullPageNftDetailModalListener />
{isProd ? null : <Debugger />}
{children}
</BottomSheetProvider>
</GlobalLayoutContextProvider>
</SnowProvider>
</SearchProvider>
Expand Down
123 changes: 123 additions & 0 deletions apps/web/src/contexts/bottomsheet/BottomSheetContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import React, { createContext, memo, useCallback, useContext, useMemo, useState } from 'react';
import colors from 'shared/theme/colors';
import styled from 'styled-components';
import { Drawer } from 'vaul';

type BottomSheetState = {
isBottomSheetModalVisible: boolean;
};
const BottomSheetStateContext = createContext<BottomSheetState | undefined>(undefined);

export const useBottomSheetModalState = (): BottomSheetState => {
const context = useContext(BottomSheetStateContext);
if (!context) {
throw new Error('Attempted to use BottomSheetModalStateContext without a provider!');
}
return context;
};

type BottomSheetActions = {
showBottomSheet: (bottomSheetModal: BottomSheet) => void;
hideBottomSheet: () => void;
};

const BottomSheetActionsContext = createContext<BottomSheetActions | undefined>(undefined);

export const useBottomSheetActions = (): BottomSheetActions => {
const context = useContext(BottomSheetActionsContext);
if (!context) {
throw new Error('Attempted to use BottomSheetModalActionsContext without a provider!');
}
return context;
};

type BottomSheetModalProviderProps = {
children: React.ReactNode;
};

type BottomSheet = {
content: React.ReactNode;
};

function BottomSheetProvider({ children }: BottomSheetModalProviderProps) {
const [bottomSheetModalContent, setBottomSheetModalContent] = useState<BottomSheet>();
const [open, setOpen] = useState(false);

const showBottomSheet = useCallback((modal: BottomSheet) => {
setBottomSheetModalContent(modal);
setOpen(true);
}, []);

const hideBottomSheet = useCallback(() => {
setOpen(false);

// delay clearing the content to allow the modal to animate out
setTimeout(() => {
setBottomSheetModalContent(undefined);
}, 300);
}, []);

const actions = useMemo(
() => ({
showBottomSheet,
hideBottomSheet,
}),
[showBottomSheet, hideBottomSheet]
);

return (
<BottomSheetActionsContext.Provider value={actions}>
{bottomSheetModalContent && (
<Drawer.Root open={open} onClose={hideBottomSheet} dismissible shouldScaleBackground>
<Drawer.Portal>
<StyledOverlay onClick={hideBottomSheet} />
<StyledContent>
<StyledDragHandleWrapper>
<StyledDragHandle />
</StyledDragHandleWrapper>
{bottomSheetModalContent.content}
</StyledContent>
</Drawer.Portal>
</Drawer.Root>
)}
{children}
</BottomSheetActionsContext.Provider>
);
}

export default memo(BottomSheetProvider);

const StyledOverlay = styled(Drawer.Overlay)`
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 40;
background-color: rgba(0, 0, 0, 0.55);
backdrop-filter: blur(2px);
`;

const StyledContent = styled(Drawer.Content)`
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 50;
margin-top: 24px;
border-top-left-radius: 24px;
border-top-right-radius: 24px;
background-color: white;
padding: 16px 24px;
`;

const StyledDragHandleWrapper = styled.div`
padding: 8px 16px 16px 16px;
`;
const StyledDragHandle = styled.div`
border-radius: 4px;
background-color: ${colors.porcelain};
height: 4px;
width: 83px;
margin: 0 auto;
`;
16 changes: 15 additions & 1 deletion apps/web/src/scenes/LandingPage/LandingCoverAnimation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import breakpoints from '~/components/core/breakpoints';
import { Button } from '~/components/core/Button/Button';
import { HStack, VStack } from '~/components/core/Spacer/Stack';
import { TitleCondensed, TitleXS } from '~/components/core/Text/Text';
import { useBottomSheetActions } from '~/contexts/bottomsheet/BottomSheetContext';
import useAuthModal from '~/hooks/useAuthModal';
import useWindowSize, { useIsMobileOrMobileLargeWindowWidth } from '~/hooks/useWindowSize';

import Image from '../WelcomeAnimation/Image';
import { AnimatedImage } from '../WelcomeAnimation/Images';
import { animatedImages } from './LandingCoverAnimationImages';
import { MobileAppsUpsellBottomSheet } from './MobileAppsUpsellBottomSheet';

type AspectRatio = 'vertical' | 'horizontal' | undefined;
const calc = (x: number, y: number) => [(x - window.innerWidth) / 2, (y - window.innerHeight) / 2];
Expand Down Expand Up @@ -59,6 +61,18 @@ export default function LandingCoverAnimation() {
const isMobile = useIsMobileOrMobileLargeWindowWidth();
const showAuthModal = useAuthModal('sign-up');

const { showBottomSheet } = useBottomSheetActions();

const handleClickCta = useCallback(() => {
if (isMobile) {
showBottomSheet({
content: <MobileAppsUpsellBottomSheet onContinueInBrowserClick={showAuthModal} />,
});
return;
}
showAuthModal();
}, [isMobile, showAuthModal, showBottomSheet]);

return (
<StyledContainer onMouseMove={({ clientX: x, clientY: y }) => set({ xy: calc(x, y) })}>
<animated.div
Expand All @@ -78,7 +92,7 @@ export default function LandingCoverAnimation() {
eventContext={contexts.Onboarding}
eventFlow={flows['Web Signup Flow']}
properties={{ buttonLocation: 'Landing Page Splash Screen' }}
onClick={showAuthModal}
onClick={handleClickCta}
>
<StyledCtaText color={colors.white}>Get started</StyledCtaText>
</StyledButton>
Expand Down
61 changes: 61 additions & 0 deletions apps/web/src/scenes/LandingPage/MobileAppsUpsellBottomSheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import AppleLogo from 'public/icons/apple_logo.svg';
import { useCallback } from 'react';
import { contexts } from 'shared/analytics/constants';
import styled from 'styled-components';

import { Button } from '~/components/core/Button/Button';
import { HStack, VStack } from '~/components/core/Spacer/Stack';
import { BaseM, TitleCondensed } from '~/components/core/Text/Text';
import { useBottomSheetActions } from '~/contexts/bottomsheet/BottomSheetContext';

import { APP_STORE_URL } from './LandingPage';

type Props = {
onContinueInBrowserClick: () => void;
};

export function MobileAppsUpsellBottomSheet({ onContinueInBrowserClick }: Props) {
const { hideBottomSheet } = useBottomSheetActions();

const handleDownloadAppClick = useCallback(() => {
window.open(APP_STORE_URL, '_blank');
}, []);

const handleContinueInBrowserClick = useCallback(() => {
hideBottomSheet();
onContinueInBrowserClick();
}, [hideBottomSheet, onContinueInBrowserClick]);

return (
<StyledWrapper gap={32}>
<VStack gap={24}>
<StyledTitle>Introducing the Gallery mobile app</StyledTitle>
<BaseM>Theres no better experience than the official Gallery app on mobile.</BaseM>
</VStack>
<VStack gap={16}>
<Button
eventElementId="Mobile Landing Page Bottom Sheet Download App Button"
eventName="Mobile Landing Page Bottom Sheet Download App Button Click"
eventContext={contexts['Mobile App Upsell']}
onClick={handleDownloadAppClick}
>
<HStack gap={8} align="center">
<AppleLogo width={27} height={27} />
Download APP
</HStack>
</Button>
<BaseM onClick={handleContinueInBrowserClick}>Continue in browser</BaseM>
</VStack>
</StyledWrapper>
);
}

const StyledWrapper = styled(VStack)`
text-align: center;
padding: 16px 0;
`;

const StyledTitle = styled(TitleCondensed)`
font-size: 48px;
line-height: 1;
`;
Loading

0 comments on commit 0b631f5

Please sign in to comment.