Skip to content

Commit

Permalink
Merge pull request #1903 from lumi-tip/development-lumi-8532
Browse files Browse the repository at this point in the history
♻️ refactor of bootcamp landing componentes
  • Loading branch information
tommygonzaleza authored Mar 10, 2025
2 parents d204a1a + da43152 commit 9b7e540
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 92 deletions.
13 changes: 7 additions & 6 deletions src/common/components/CouponTopBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { Box, Flex } from '@chakra-ui/react';
import useTranslation from 'next-translate/useTranslation';
import Text from './Text';
import Timer from './Timer';
import NextChakraLink from './NextChakraLink';
import useStyle from '../hooks/useStyle';
import useSignup from '../store/actions/signupAction';
import NextChakraLink from './NextChakraLink';

function CouponTopBar() {
function CouponTopBar({ ...rest }) {
const { t } = useTranslation('course');
const { hexColor } = useStyle();
const { getPriceWithDiscount, setSelfAppliedCoupon, state } = useSignup();
Expand Down Expand Up @@ -48,14 +48,15 @@ function CouponTopBar() {
<Box
background={hexColor.green}
padding="8px 10px"
{...rest}
>
<Box maxWidth="1280px" margin="auto" display="flex" justifyContent="space-between" alignItems="center">
<Flex alignItems="center" gap="10px" flexDirection="row" flexWrap="wrap" grow={1} justifyContent="center">
<Text color="#FFF" fontSize={{ base: '13px', md: '18px' }} fontFamily="inter">
<Flex alignItems="center" justifyContent="center" grow={1} gap="10px" flexDirection="row" flexWrap="wrap">
<Text color="#FFF" fontSize={{ base: '12px', md: '18px' }} fontFamily="inter">
{t('coupon-bar.headline', { discount })}
</Text>
<Flex gap="10px">
<Text color="#FFF" fontSize={{ base: '13px', md: '17px' }} fontFamily="inter" fontWeight="900">
<Text color="#FFF" fontSize={{ base: '12px', md: '17px' }} fontFamily="inter" fontWeight="900">
{t('coupon-bar.ends-in', { time: '' })}
</Text>
<Timer
Expand All @@ -65,7 +66,7 @@ function CouponTopBar() {
onFinish={() => setSelfAppliedCoupon(null)}
color="white"
background="none"
fontSize={{ base: '13px', md: '17px' }}
fontSize={{ base: '12px', md: '17px' }}
fontFamily="inter"
fontWeight="900"
/>
Expand Down
8 changes: 5 additions & 3 deletions src/common/components/CustomCarousel.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { useState, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { Flex, Box, Text, Image, Badge } from '@chakra-ui/react';
import useTranslation from 'next-translate/useTranslation';
import Icon from './Icon';
import useStyle from '../hooks/useStyle';

function CustomCarousel({ assignmentList }) {
const { t } = useTranslation();
const { borderColorStrong, backgroundColor, lightColor, fontColor } = useStyle();
const [currentSlide, setCurrentSlide] = useState(0);
const totalSlides = assignmentList?.length;
Expand Down Expand Up @@ -101,7 +103,7 @@ function CustomCarousel({ assignmentList }) {

<Flex flex="1" flexDirection="column" gridGap="10px" justifyContent="space-between">
<Flex gridGap="8px" justifyContent="space-between" alignItems="flex-start">
<Flex gap="10px" flexWrap="wrap">
<Flex gap="5px" flexWrap="wrap" flexGrow="1" alignItems="center">
{assignmentList[currentSlide].technologies.map((tech) => (
<Box>
{tech.icon_url ? (
Expand All @@ -113,7 +115,7 @@ function CustomCarousel({ assignmentList }) {
))}
</Flex>

<Flex gridGap="16px" alignItems="center">
<Flex alignItems="center">
<Badge
borderRadius="20px"
display="flex"
Expand Down Expand Up @@ -141,7 +143,7 @@ function CustomCarousel({ assignmentList }) {
borderRadius="10px"
padding="3px 5px"
>
{assignmentList[currentSlide].difficulty.charAt(0) + assignmentList[currentSlide].difficulty.slice(1).toLowerCase()}
{t(`common:${assignmentList[currentSlide].difficulty.toLowerCase()}`)}
</Badge>
<Flex gap="10px">
<Icon icon="rigobot-avatar-tiny" width="18px" height="18px" />
Expand Down
1 change: 1 addition & 0 deletions src/common/components/MktTwoColumnSideImage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ function MktTwoColumnSideImage({
src={imageUrl}
alt={imageAlt}
title={imageAlt}
px={{ base: '10px', md: 'none' }}
borderRadius="3px"
width={imageProps?.width}
{...imageSideProps}
Expand Down
173 changes: 109 additions & 64 deletions src/js_modules/projects/FixedBottomCta.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,36 @@ import {
Box,
Button,
Text,
Skeleton,
} from '@chakra-ui/react';
import PropTypes from 'prop-types';
import useTranslation from 'next-translate/useTranslation';
import Heading from '../../common/components/Heading';
import useStyle from '../../common/hooks/useStyle';
import ReactPlayerV2 from '../../common/components/ReactPlayerV2';
import CouponTopBar from '../../common/components/CouponTopBar';

function StickyBottomCta({ asset, onClick, isCtaVisible, course, videoUrl, couponApplied, financingAvailable, isAuthenticated, paymentOptions, ...rest }) {
function StickyBottomCta({
asset,
onClick,
isCtaVisible,
course,
videoUrl,
couponApplied,
financingAvailable,
isFetching,
isAuthenticated,
paymentOptions,
...rest
}) {
const { t } = useTranslation('exercises');
const { hexColor } = useStyle();

const includesFreeTier = paymentOptions?.some((option) => option.isFreeTier);

if (!isCtaVisible) return null;

const includesFreeTier = paymentOptions?.some((option) => option.isFreeTier);
const backgroundColor = couponApplied ? hexColor.greenLight4 : hexColor.backgroundColor;

const getHeadingForAsset = () => {
if (!isAuthenticated && videoUrl) return t('video-instructions');
if (isAuthenticated && videoUrl) return t('video-instructions-logged');
Expand All @@ -30,86 +45,116 @@ function StickyBottomCta({ asset, onClick, isCtaVisible, course, videoUrl, coupo
return t('start-interactive-cta');
};

const courseButtonText = () => {
if (videoUrl) return t('course:join-cohort');
if (financingAvailable) return t('common:see-financing-options');
return t('common:enroll');
};

return (
<>
<Box
overflow="hidden"
position="fixed"
width="100vw"
bottom="0"
zIndex="100"
borderRadius="11px 11px 0 0"
border="1px solid"
borderColor={hexColor.greenLight}
background={couponApplied ? hexColor.greenLight4 : hexColor.backgroundColor}
textAlign="center"
display={{ base: 'block', md: 'none' }}
{...rest}
>
<Box paddingBottom="20px">
{videoUrl && (
<ReactPlayerV2
title={asset && 'Video tutorial'}
withModal
url={videoUrl}
withThumbnail
thumbnailStyle={{
borderRadius: '0 0 0 0',
height: '110px',
}}
/>
)}
{asset && (
<>
<Heading size="sm" mt="10px">
{getHeadingForAsset()}
</Heading>
<Button display="block" width="95%" margin="10px auto" color="white" background={hexColor.greenLight} onClick={onClick}>
{getButtonTextForAsset()}
<Box
overflow="hidden"
position="fixed"
width="100vw"
bottom="0"
zIndex="100"
borderRadius="11px 11px 0 0"
border="1px solid"
borderColor={hexColor.greenLight}
background={backgroundColor}
textAlign="center"
display={{ base: 'block', md: 'none' }}
{...rest}
>
<Box paddingBottom={couponApplied || isFetching ? '0' : '5px'}>

{videoUrl && (
<ReactPlayerV2
title={asset ? 'Video tutorial' : ''}
withModal
url={videoUrl}
withThumbnail
thumbnailStyle={{ borderRadius: '0 0 0 0', height: '110px' }}
/>
)}

{asset && (
<>
<Heading size="sm" mt="10px">{getHeadingForAsset()}</Heading>
<Button display="block" width="95%" margin="10px auto" color="white" background={hexColor.greenLight} onClick={onClick}>
{getButtonTextForAsset()}
</Button>
</>
)}

{course && isFetching && <Skeleton height="150px" width="100%" padding="1px" />}

{course && !isFetching && couponApplied && <CouponTopBar />}

{course && !isFetching && !couponApplied && (
<>
{!videoUrl && (
<Text color="black" fontSize="18px" fontWeight={600}>
{t('course:create-account-text')}
</Text>
)}

<Button
fontSize="18px"
display="block"
width="95%"
margin="10px auto"
border={`1px solid ${hexColor.greenLight}`}
color={hexColor.greenLight}
background={hexColor.backgroundColor}
onClick={onClick}
>
{courseButtonText()}
</Button>

{includesFreeTier && (
<Button
fontSize="18px"
display="block"
width="95%"
margin="10px auto"
color="white"
background={hexColor.greenLight}
onClick={onClick}
>
{t('common:start-free-trial')}
</Button>
</>
)}
{course && (
<>
<Heading size="21px" color="black" pt="10px">{t('course:join-cohort')}</Heading>
{!videoUrl && (
<>
<Text color="black">{t('course:create-account-text')}</Text>
<Button fontSize="18px" display="block" width="95%" margin="10px auto" border={`1px solid ${hexColor.greenLight}`} color={hexColor.greenLight} background={hexColor.backgroundColor} onClick={onClick}>
{financingAvailable ? t('common:see-financing-options') : t('common:enroll')}
</Button>
</>
)}
{includesFreeTier && (
<Button fontSize="18px" display="block" width="95%" margin="10px auto" color="white" background={hexColor.greenLight} onClick={onClick}>
{t('common:start-free-trial')}
</Button>
)}
</>
)}
</Box>
)}
</>
)}

</Box>
</>
</Box>
);
}

StickyBottomCta.propTypes = {
asset: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])).isRequired,
course: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])).isRequired,
asset: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
course: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
videoUrl: PropTypes.string,
couponApplied: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
onClick: PropTypes.func.isRequired,
isCtaVisible: PropTypes.bool.isRequired,
financingAvailable: PropTypes.string,
isAuthenticated: PropTypes.bool,
paymentOptions: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any]))).isRequired,
isFetching: PropTypes.bool,
paymentOptions: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.any])),
};

StickyBottomCta.defaultProps = {
couponApplied: undefined,
asset: null,
course: null,
videoUrl: undefined,
couponApplied: undefined,
financingAvailable: undefined,
isAuthenticated: false,
isFetching: false,
paymentOptions: [],
};

export default StickyBottomCta;
43 changes: 24 additions & 19 deletions src/pages/bootcamp/[course_slug].jsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ function CoursePage({ data, syllabus }) {
const [coupon] = usePersistentBySession('coupon', '');
const { selfAppliedCoupon } = state;
const showBottomCTA = useRef(null);
const [isCtaVisible, setIsCtaVisible] = useState(true);
const [isCtaVisible, setIsCtaVisible] = useState(false);
const [allDiscounts, setAllDiscounts] = useState([]);
const { isAuthenticated, user, logout, cohorts } = useAuth();
const { hexColor, backgroundColor, fontColor, borderColor, complementaryBlue, featuredColor } = useStyle();
Expand Down Expand Up @@ -243,22 +243,25 @@ function CoursePage({ data, syllabus }) {
};

useEffect(() => {
if (isWindow) {
const handleScroll = () => {
if (showBottomCTA.current) {
const { scrollY } = window;
const top = getElementTopOffset(showBottomCTA.current);
setIsCtaVisible(top - scrollY > 700);
}
};
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}
const checkCtaVisibility = () => {
if (showBottomCTA.current) {
const { scrollY } = window;
const top = getElementTopOffset(showBottomCTA.current);
setIsCtaVisible(top - scrollY > 700);
}
};

return undefined;
}, [isWindow]);
checkCtaVisibility();

const handleScroll = () => {
checkCtaVisibility();
};

window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);

const joinCohort = () => {
if (isAuthenticated && existsRelatedSubscription) {
Expand Down Expand Up @@ -538,6 +541,7 @@ function CoursePage({ data, syllabus }) {
</Head>
)}
<FixedBottomCta
isFetching={initialDataIsFetching}
isCtaVisible={isCtaVisible}
financingAvailable={planData?.financingOptions?.length > 0}
videoUrl={data?.course_translation?.video_url}
Expand All @@ -547,9 +551,10 @@ function CoursePage({ data, syllabus }) {
couponApplied={selfAppliedCoupon}
width="calc(100vw - 15px)"
left="7.5px"
zIndex={1100}
/>
<CouponTopBar />
<Flex flexDirection="column" mt="2rem">
<CouponTopBar display={{ base: 'none', md: 'block' }} />
<Flex flexDirection="column" mt={{ base: '0', md: '0.5rem' }}>
<GridContainer maxWidth="1280px" gridTemplateColumns="repeat(12, 1fr)" gridGap="36px" padding="8px 10px 50px 10px" mt="17px">
<Flex flexDirection="column" gridColumn="1 / span 8" gridGap="24px">
{/* Title */}
Expand Down Expand Up @@ -644,6 +649,7 @@ function CoursePage({ data, syllabus }) {
<Flex flexDirection="column" gridColumn="9 / span 4" mt={{ base: '2rem', md: '0' }} ref={showBottomCTA}>
<ShowOnSignUp
title={getAlternativeTranslation('join-cohort')}
alignSelf="center"
maxWidth="396px"
description={isAuthenticated ? getAlternativeTranslation('join-cohort-description') : getAlternativeTranslation('create-account-text')}
borderColor={data.color || 'green.400'}
Expand All @@ -664,7 +670,6 @@ function CoursePage({ data, syllabus }) {
invertHandlerPosition
headContent={data?.course_translation?.video_url && (
<Flex flexDirection="column" position="relative">
{/* <Image src={data?.icon_url} top="-1.5rem" left="-1.5rem" width="64px" height="64px" objectFit="cover" position="absolute" /> */}
<ReactPlayerV2
url={data?.course_translation?.video_url}
withThumbnail
Expand Down

0 comments on commit 9b7e540

Please sign in to comment.