From 37ac223949efa993afc990e4e820c24e7aad7c5c Mon Sep 17 00:00:00 2001 From: japhethLG Date: Thu, 9 May 2024 18:10:26 +0800 Subject: [PATCH 1/2] feat: Pricing Page Revamp --- .../revamp/AdditionalFeatures/FeatureCard.js | 98 ++++++++++ .../revamp/AdditionalFeatures/index.js | 125 ++++++++++++ src/blocks/pricing/revamp/Brands/index.js | 119 ++++++++++++ src/blocks/pricing/revamp/FAQs/index.js | 119 ++++++++++++ .../pricing/revamp/PricingHero/index.js | 45 +++++ .../pricing/revamp/PricingTable/BodyCell.js | 74 +++++++ .../pricing/revamp/PricingTable/EmptyCell.js | 32 ++++ .../pricing/revamp/PricingTable/HeaderCell.js | 76 ++++++++ .../revamp/PricingTable/TierSelector.js | 46 +++++ .../pricing/revamp/PricingTable/index.js | 177 +++++++++++++++++ .../PricingTierCards/PricingTierCard.js | 180 ++++++++++++++++++ .../pricing/revamp/PricingTierCards/index.js | 121 ++++++++++++ .../revamp/Testimonial/TestimonialCard.js | 82 ++++++++ .../pricing/revamp/Testimonial/index.js | 133 +++++++++++++ .../ui/GetDemoSection/MultiFieldForm.js | 2 +- src/revamp/ui/TabsSection/index.js | 2 +- src/views/zesty/Pricing.js | 152 +++++---------- 17 files changed, 1476 insertions(+), 107 deletions(-) create mode 100644 src/blocks/pricing/revamp/AdditionalFeatures/FeatureCard.js create mode 100644 src/blocks/pricing/revamp/AdditionalFeatures/index.js create mode 100644 src/blocks/pricing/revamp/Brands/index.js create mode 100644 src/blocks/pricing/revamp/FAQs/index.js create mode 100644 src/blocks/pricing/revamp/PricingHero/index.js create mode 100644 src/blocks/pricing/revamp/PricingTable/BodyCell.js create mode 100644 src/blocks/pricing/revamp/PricingTable/EmptyCell.js create mode 100644 src/blocks/pricing/revamp/PricingTable/HeaderCell.js create mode 100644 src/blocks/pricing/revamp/PricingTable/TierSelector.js create mode 100644 src/blocks/pricing/revamp/PricingTable/index.js create mode 100644 src/blocks/pricing/revamp/PricingTierCards/PricingTierCard.js create mode 100644 src/blocks/pricing/revamp/PricingTierCards/index.js create mode 100644 src/blocks/pricing/revamp/Testimonial/TestimonialCard.js create mode 100644 src/blocks/pricing/revamp/Testimonial/index.js diff --git a/src/blocks/pricing/revamp/AdditionalFeatures/FeatureCard.js b/src/blocks/pricing/revamp/AdditionalFeatures/FeatureCard.js new file mode 100644 index 000000000..7c0f9ff26 --- /dev/null +++ b/src/blocks/pricing/revamp/AdditionalFeatures/FeatureCard.js @@ -0,0 +1,98 @@ +import React from 'react'; +import { Button, Card, Typography, Grid, Box } from '@mui/material'; +import { ThemeProvider, useTheme } from '@emotion/react'; + +import revampTheme from 'theme/revampTheme'; +import FillerContent from 'components/globals/FillerContent'; + +const FeatureCard = ({ feature }) => { + const { + image, + title, + description, + primary_cta_label, + secondary_cta_label, + primary_cta_link, + secondary_cta_link, + } = feature; + const theme = useTheme(); + + return ( + revampTheme(theme.palette.mode)}> + + + + {title} + + + {description} + + + + + + + + + + + + ); +}; + +export default FeatureCard; diff --git a/src/blocks/pricing/revamp/AdditionalFeatures/index.js b/src/blocks/pricing/revamp/AdditionalFeatures/index.js new file mode 100644 index 000000000..e14e74fd0 --- /dev/null +++ b/src/blocks/pricing/revamp/AdditionalFeatures/index.js @@ -0,0 +1,125 @@ +import React from 'react'; +import { useTheme } from '@emotion/react'; +import { + Typography, + Box, + useMediaQuery, + MobileStepper, + Button, +} from '@mui/material'; +import { KeyboardArrowLeft, KeyboardArrowRight } from '@mui/icons-material'; + +import FeatureCard from './FeatureCard'; + +const AdditionalFeatures = ({ features, title }) => { + const theme = useTheme(); + + const isLargeScreen = useMediaQuery(theme.breakpoints.up('lg')); + const isMediumScreen = useMediaQuery(theme.breakpoints.up('md')); + + const [activeStep, setActiveStep] = React.useState(0); + + const featuresPerPage = isLargeScreen ? 3 : isMediumScreen ? 2 : 1; + const maxSteps = Math.ceil(features.length / featuresPerPage); + + const handleNext = () => { + setActiveStep((prevActiveStep) => prevActiveStep + 1); + }; + + const handleBack = () => { + setActiveStep((prevActiveStep) => prevActiveStep - 1); + }; + + const startIndex = Math.max( + 0, + activeStep * featuresPerPage - (featuresPerPage - 1), + ); + const endIndex = Math.min(features.length, startIndex + featuresPerPage); + const currentFeatures = features.slice(startIndex, endIndex); + return ( + + + {title} + + + + {currentFeatures.map((feature) => ( + + ))} + + + + {theme.direction === 'rtl' ? ( + + ) : ( + + )} + + } + backButton={ + + } + /> + + ); +}; + +export default AdditionalFeatures; diff --git a/src/blocks/pricing/revamp/Brands/index.js b/src/blocks/pricing/revamp/Brands/index.js new file mode 100644 index 000000000..916b74613 --- /dev/null +++ b/src/blocks/pricing/revamp/Brands/index.js @@ -0,0 +1,119 @@ +import { Box, Card, CardContent, Typography, useTheme } from '@mui/material'; + +import FillerContent from 'components/globals/FillerContent'; +import Container from 'blocks/container/Container'; +import ZestyImage from 'blocks/Image/ZestyImage'; + +const Brands = ({ + logoItems = FillerContent.logos, + heading_text = '', + textOutside = false, + maxWidth = 1500, + variant = 'contained', + invertLogo = true, + background = 'transparent', + marginTop = 0, +}) => { + const theme = useTheme(); + const isDarkMode = theme.palette.mode === 'dark'; + const sunsDarkLogoUrl = + 'https://kfg6bckb.media.zestyio.com/sunsdark.1fc97b3c326478bf6afcb60e52679656.png?width=241'; + + // check if features_header richtext if not convert it to richtext format for consistency + const htmlCheck = new RegExp('<("[^"]*"|\'[^\']*\'|[^\'">])*>'); + const isRichText = htmlCheck.test(heading_text); + + if (!isRichText && heading_text != '') { + heading_text = `

${heading_text}

`; + } + + return ( + + + {textOutside && ( + + More than 10 million people across the world choose us + + )} + + + {!textOutside && ( + + + More than 10 million people across the world choose us + + + )} + + {logoItems?.map((item, index) => ( + + + + ))} + + + + + + ); +}; + +export default Brands; diff --git a/src/blocks/pricing/revamp/FAQs/index.js b/src/blocks/pricing/revamp/FAQs/index.js new file mode 100644 index 000000000..b7cb4623f --- /dev/null +++ b/src/blocks/pricing/revamp/FAQs/index.js @@ -0,0 +1,119 @@ +import React, { useState } from 'react'; + +import { + Accordion, + AccordionDetails, + AccordionSummary, + Box, + Container, + Grid, + Typography, + useTheme, +} from '@mui/material'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import MuiMarkdown from 'markdown-to-jsx'; + +const FAQs = ({ faqs, title, subtitle }) => { + const theme = useTheme(); + const [expanded, setExpanded] = useState(false); + + const handleChange = (panel) => (event, isExpanded) => { + setExpanded(isExpanded ? panel : false); + }; + + if (!faqs) return null; + return ( + + + + + + {title} + + + + {subtitle === '' && subtitle && ( + + + {subtitle} + + + )} + + + {faqs.map((faq, index) => ( + + } + aria-controls={`panel${index + 1}a-content`} + id={`panel${index + 1}a-header`} + sx={{ height: '80px', minHeight: '80px' }} + > + + {faq.question} + + + + + {faq.answer} + + + + ))} + + + + ); +}; + +export default FAQs; diff --git a/src/blocks/pricing/revamp/PricingHero/index.js b/src/blocks/pricing/revamp/PricingHero/index.js new file mode 100644 index 000000000..b38abbf21 --- /dev/null +++ b/src/blocks/pricing/revamp/PricingHero/index.js @@ -0,0 +1,45 @@ +import { Typography, useTheme } from '@mui/material'; + +import Container from 'blocks/container/Container'; + +const PricingHero = ({ title, subtitle, tiers = [] }) => { + const theme = useTheme(); + + return ( + + + {subtitle} + + + {title} + + + ); +}; + +export default PricingHero; diff --git a/src/blocks/pricing/revamp/PricingTable/BodyCell.js b/src/blocks/pricing/revamp/PricingTable/BodyCell.js new file mode 100644 index 000000000..1988812cd --- /dev/null +++ b/src/blocks/pricing/revamp/PricingTable/BodyCell.js @@ -0,0 +1,74 @@ +import { + Box, + TableCell, + Typography, + useMediaQuery, + useTheme, +} from '@mui/material'; +import CheckIcon from '@mui/icons-material/Check'; +import CloseIcon from '@mui/icons-material/Close'; + +const renderValue = (value) => { + const theme = useTheme(); + + if (value === '/') { + return ( + + ); + } else if (value === 'x') { + return ( + + ); + } else { + return ( + + {value} + + ); + } +}; + +const BodyCell = ({ data, tier, selectedTier }) => { + const theme = useTheme(); + + const isSmall = useMediaQuery(theme.breakpoints.up('sm')); + + if (selectedTier !== tier && !isSmall) { + return <>; + } + return ( + + + {renderValue(data)} + + + ); +}; + +export default BodyCell; diff --git a/src/blocks/pricing/revamp/PricingTable/EmptyCell.js b/src/blocks/pricing/revamp/PricingTable/EmptyCell.js new file mode 100644 index 000000000..7cf6603d9 --- /dev/null +++ b/src/blocks/pricing/revamp/PricingTable/EmptyCell.js @@ -0,0 +1,32 @@ +import { Box, TableCell, useMediaQuery, useTheme } from '@mui/material'; + +const EmptyCell = ({ tier, selectedTier }) => { + const theme = useTheme(); + + const isSmall = useMediaQuery(theme.breakpoints.up('sm')); + + if (selectedTier !== tier && !isSmall) { + return <>; + } + + return ( + + + + ); +}; + +export default EmptyCell; diff --git a/src/blocks/pricing/revamp/PricingTable/HeaderCell.js b/src/blocks/pricing/revamp/PricingTable/HeaderCell.js new file mode 100644 index 000000000..d4641439b --- /dev/null +++ b/src/blocks/pricing/revamp/PricingTable/HeaderCell.js @@ -0,0 +1,76 @@ +import { + useMediaQuery, + useTheme, + TableCell, + Box, + Typography, + Button, +} from '@mui/material'; + +const HeaderCell = ({ data, selectedTier }) => { + const theme = useTheme(); + + const isSmall = useMediaQuery(theme.breakpoints.up('sm')); + + if (selectedTier !== data.title && !isSmall) { + return <>; + } + + return ( + + + {isSmall && ( + + {data.title} + + )} + + {data.price} + + + + + ); +}; + +export default HeaderCell; diff --git a/src/blocks/pricing/revamp/PricingTable/TierSelector.js b/src/blocks/pricing/revamp/PricingTable/TierSelector.js new file mode 100644 index 000000000..806b123b1 --- /dev/null +++ b/src/blocks/pricing/revamp/PricingTable/TierSelector.js @@ -0,0 +1,46 @@ +import { ToggleButton, ToggleButtonGroup, useTheme } from '@mui/material'; + +const TierSelector = ({ tiers, selectedTier, setSelectedTier }) => { + const theme = useTheme(); + + const handleTierChange = (event, tier) => { + setSelectedTier(tier); + }; + + return ( + + {tiers.map((tier) => ( + + {tier.title} + + ))} + + ); +}; + +export default TierSelector; diff --git a/src/blocks/pricing/revamp/PricingTable/index.js b/src/blocks/pricing/revamp/PricingTable/index.js new file mode 100644 index 000000000..b58ba03a8 --- /dev/null +++ b/src/blocks/pricing/revamp/PricingTable/index.js @@ -0,0 +1,177 @@ +import React, { useState } from 'react'; + +import { + Box, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, + useMediaQuery, + useTheme, +} from '@mui/material'; +import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; + +import HeaderCell from './HeaderCell'; +import BodyCell from './BodyCell'; +import EmptyCell from './EmptyCell'; +import TierSelector from './TierSelector'; + +const PricingTable = ({ levers, classification, tiers }) => { + const theme = useTheme(); + + const [selectedTier, setSelectedTier] = useState(tiers[0].title); + + const tierSelectorProps = { + tiers, + selectedTier, + setSelectedTier, + }; + + const isSmall = useMediaQuery(theme.breakpoints.down('sm')); + + if (!levers && !classification && !tiers) return <>; + + return ( + + + + + + + + {isSmall && } + + + {tiers.map((tier) => ( + + ))} + + + + {classification?.map((item, index) => ( + <> + + + + {item.title} + + + {tiers.map((tier) => ( + + ))} + + {levers + ?.filter((lever) => lever.classification[0] === item.title) + .map((lever) => ( + <> + + + + + + {lever.title} + + + + + + + + + ))} + + + {tiers.map((tier) => ( + + ))} + + + ))} + +
+
+
+ ); +}; + +export default PricingTable; diff --git a/src/blocks/pricing/revamp/PricingTierCards/PricingTierCard.js b/src/blocks/pricing/revamp/PricingTierCards/PricingTierCard.js new file mode 100644 index 000000000..923deaf9e --- /dev/null +++ b/src/blocks/pricing/revamp/PricingTierCards/PricingTierCard.js @@ -0,0 +1,180 @@ +import { + Box, + Button, + Card, + Divider, + Typography, + useTheme, +} from '@mui/material'; +import MuiMarkdown from 'markdown-to-jsx'; + +export const PricingTierCard = ({ tier }) => { + const theme = useTheme(); + const { + title, + description, + price, + primary_cta_label, + primary_cta_link, + secondary_cta_label, + secondary_cta_link, + main_features, + key_features, + } = tier; + return ( + + + + {title} + + + {description} + + + {price} + + + + + {main_features && ( + + {main_features} + + )} + + + {title === 'Free' + ? 'Key Features' + : title === 'Growth' + ? 'Everything in Free, plus' + : 'Everythin in Growth, plus'} + + {key_features && ( + + {key_features} + + )} + + + ); +}; diff --git a/src/blocks/pricing/revamp/PricingTierCards/index.js b/src/blocks/pricing/revamp/PricingTierCards/index.js new file mode 100644 index 000000000..03b7bc7ca --- /dev/null +++ b/src/blocks/pricing/revamp/PricingTierCards/index.js @@ -0,0 +1,121 @@ +import { useState } from 'react'; + +import { + ToggleButtonGroup, + ToggleButton, + Container, + Grid, + Button, + useTheme, +} from '@mui/material'; + +import { PricingTierCard } from './PricingTierCard'; + +export const PricingTierCards = ({ pricingTiers }) => { + const theme = useTheme(); + + const [billingCycle, setBillingCycle] = useState('yearly'); + + const handleBillingChange = (event, newBillingCycle) => { + setBillingCycle(newBillingCycle); + }; + + const scrollToPricingTable = () => { + const element = document.getElementById('pricing-table'); + if (element) { + const rect = element.getBoundingClientRect(); + const scrollTop = + window.pageYOffset || document.documentElement.scrollTop; + window.scrollTo({ + top: rect.top + scrollTop - 50, + behavior: 'smooth', + }); + } + }; + + return ( + + + + Yearly + + + Monthly + + + + {pricingTiers.map((tier, index) => ( + + + + ))} + + + + ); +}; diff --git a/src/blocks/pricing/revamp/Testimonial/TestimonialCard.js b/src/blocks/pricing/revamp/Testimonial/TestimonialCard.js new file mode 100644 index 000000000..9e637e9f0 --- /dev/null +++ b/src/blocks/pricing/revamp/Testimonial/TestimonialCard.js @@ -0,0 +1,82 @@ +import { Card, Typography, useTheme, Box } from '@mui/material'; + +import FillerContent from 'components/globals/FillerContent'; + +const TestimonialCard = ({ feature }) => { + const theme = useTheme(); + + const { + headline, + person_name, + person_title, + company_name, + company_logo, + image, + testimonial_content, + } = feature; + + return ( + + + + {headline} + + + + {person_name} + + + {person_title} + + + + ); +}; + +export default TestimonialCard; diff --git a/src/blocks/pricing/revamp/Testimonial/index.js b/src/blocks/pricing/revamp/Testimonial/index.js new file mode 100644 index 000000000..a8ce66e31 --- /dev/null +++ b/src/blocks/pricing/revamp/Testimonial/index.js @@ -0,0 +1,133 @@ +import { useState } from 'react'; + +import { + Typography, + Box, + MobileStepper, + Button, + useTheme, + useMediaQuery, +} from '@mui/material'; + +import KeyboardArrowLeft from '@mui/icons-material/KeyboardArrowLeft'; +import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight'; + +import TestimonialCard from './TestimonialCard'; + +const Testimonial = ({ testimonials, title }) => { + const theme = useTheme(); + + const isLargeScreen = useMediaQuery(theme.breakpoints.up('lg')); + const isMediumScreen = useMediaQuery(theme.breakpoints.up('md')); + + const [activeStep, setActiveStep] = useState(0); + + const testimonialsPerPage = isLargeScreen ? 3 : isMediumScreen ? 2 : 1; + const maxSteps = Math.ceil(testimonials.length / testimonialsPerPage); + + const handleNext = () => { + setActiveStep((prevActiveStep) => prevActiveStep + 1); + }; + + const handleBack = () => { + setActiveStep((prevActiveStep) => prevActiveStep - 1); + }; + + const startIndex = Math.max( + 0, + activeStep * testimonialsPerPage - (testimonialsPerPage - 1), + ); + const endIndex = Math.min( + testimonials.length, + startIndex + testimonialsPerPage, + ); + const currentTestimonials = testimonials.slice(startIndex, endIndex); + + return ( + + + {title} + + + + {currentTestimonials.map((testimonial) => ( + + ))} + + + + {theme.direction === 'rtl' ? ( + + ) : ( + + )} + + } + backButton={ + + } + /> + + ); +}; + +export default Testimonial; diff --git a/src/revamp/ui/GetDemoSection/MultiFieldForm.js b/src/revamp/ui/GetDemoSection/MultiFieldForm.js index 371d0efad..1b918818e 100644 --- a/src/revamp/ui/GetDemoSection/MultiFieldForm.js +++ b/src/revamp/ui/GetDemoSection/MultiFieldForm.js @@ -14,7 +14,7 @@ export const MultiFieldForm = ({ isLong, isContact, inquiryReasons, - cta = 'Book Demo', + cta = 'Contact Sales', id, }) => { return ( diff --git a/src/revamp/ui/TabsSection/index.js b/src/revamp/ui/TabsSection/index.js index a5981d78e..d5dfb4844 100644 --- a/src/revamp/ui/TabsSection/index.js +++ b/src/revamp/ui/TabsSection/index.js @@ -203,7 +203,7 @@ const TabsSection = ({ return list[nextIndex]?.name; }); } - }, 2500); + }, 5000); return () => clearInterval(interval); }, [list, trigger]); diff --git a/src/views/zesty/Pricing.js b/src/views/zesty/Pricing.js index be99bdb56..13a65bfc4 100644 --- a/src/views/zesty/Pricing.js +++ b/src/views/zesty/Pricing.js @@ -32,137 +32,79 @@ */ // Mui Imports -import React, { useEffect, useState } from 'react'; -import { useTheme } from '@mui/material/styles'; -import Box from '@mui/material/Box'; +import React from 'react'; // Components Import -import SimpleCardLogo from 'blocks/zesty/LogoGrid/SimpleCardLogo'; -// import Container from 'components/Container'; -import Container from 'blocks/container/Container'; -import PricingHero from '../../blocks/pricing/PricingHero/PricingHero'; -import SupportBanner from '../../blocks/pricing/SupportBanner/SupportBanner'; -import Faq from '../../blocks/pricing/Faq/Faq'; +import PricingHero from '../../blocks/pricing/revamp/PricingHero'; import useFetch from 'components/hooks/useFetch'; import FillerContent from 'components/globals/FillerContent'; +import { PricingTierCards } from 'blocks/pricing/revamp/PricingTierCards'; +import PricingTable from 'blocks/pricing/revamp/PricingTable'; +import AdditionalFeatures from 'blocks/pricing/revamp/AdditionalFeatures'; +import Testimonial from 'blocks/pricing/revamp/Testimonial'; +import FAQs from 'blocks/pricing/revamp/FAQs'; +import Brands from 'blocks/pricing/revamp/Brands'; + +const filterAdditionalFeatures = (features) => { + return features.filter((feature) => feature.classification[0] === "Add-on's"); +}; -function onlyUnique(value, index, self) { - return self.indexOf(value) === index; -} function Pricing({ content }) { - const theme = useTheme(); + const { data: levers } = useFetch( + `/-/pricing-levers-revamp.json`, + content.zestyProductionMode, + ); + + const { data: leverClassification } = useFetch( + `/-/pricing-levers-classification.json`, + content.zestyProductionMode, + ); + const heroProps = { title: content.title, subtitle: content.instance_definition, tiers: content.tiers.data, }; - const [, setCategories] = useState([]); - - const { data: pricingData } = useFetch( - `/-/pricing-levers.json`, - content.zestyProductionMode, - ); + const faqsProps = { + faqs: content?.related_faqs?.data, + title: content?.faqs_title, + subtitle: content?.faqs_subtitle, + }; - useEffect(() => { - let leverCategories = []; - pricingData.forEach((item) => { - leverCategories.push(item.classification); - }); - leverCategories.filter(onlyUnique); - let cats = [...new Set(leverCategories)]; - setCategories(cats); - }, [pricingData]); + const additionalFeaturesProps = { + features: filterAdditionalFeatures(levers), + title: content.additional_features_title, + }; - // const [active, setActive] = useState(false); - // const featuresHandler = () => { - // setActive(!active); - // }; + const pricingTableProps = { + levers: levers, + classification: leverClassification, + tiers: content.pricing_tiers_revamp.data, + }; - // const pricingCompareTableData = content.tiers.data; + const testimonialProps = { + testimonials: content.testimonials.data, + title: content.testimonial_title, + }; return ( <> - + - - {/* {Pricing Comparison Table} */} - {/* - - - - {content?.comparison_heading} - - - - {active && ( - - {categories.map((cat, idx) => ( - - ))} - - )} - - */} - - - - - - {/* */} - {/* - - */} - - - - - + + + + ); } From b739ab85651909c2113198b6f362672a5182cac5 Mon Sep 17 00:00:00 2001 From: japhethLG Date: Thu, 9 May 2024 18:21:30 +0800 Subject: [PATCH 2/2] feat: Pricing Page Revamp --- src/blocks/pricing/revamp/FAQs/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blocks/pricing/revamp/FAQs/index.js b/src/blocks/pricing/revamp/FAQs/index.js index b7cb4623f..897bb07d0 100644 --- a/src/blocks/pricing/revamp/FAQs/index.js +++ b/src/blocks/pricing/revamp/FAQs/index.js @@ -57,7 +57,7 @@ const FAQs = ({ faqs, title, subtitle }) => { - {subtitle === '' && subtitle && ( + {subtitle !== '' && subtitle && (