From 1f6f1c09d8cc9add481e2bca88ceb1e2362584ba Mon Sep 17 00:00:00 2001 From: internxt-yu Date: Tue, 4 Aug 2026 16:52:30 +0200 Subject: [PATCH 1/5] Created Photo LP in EN version, without translations and metatags --- next.config.js | 10 - src/assets/lang/en/photo.json | 164 +++++++++++++++ src/assets/types/photos.ts | 152 ++++++++++++++ src/components/photo/CoreFeaturesSection.tsx | 111 +++++++++++ src/components/photo/HeroSection.tsx | 53 +++++ .../photo/HorizontalScrollableSection.tsx | 186 ++++++++++++++++++ .../HorizontalScrollableWithPhotoSection.tsx | 176 +++++++++++++++++ src/components/photo/PhotoSection.tsx | 43 ++++ src/pages/photos.tsx | 175 ++++++++++++++++ 9 files changed, 1060 insertions(+), 10 deletions(-) create mode 100644 src/assets/lang/en/photo.json create mode 100644 src/assets/types/photos.ts create mode 100644 src/components/photo/CoreFeaturesSection.tsx create mode 100644 src/components/photo/HeroSection.tsx create mode 100644 src/components/photo/HorizontalScrollableSection.tsx create mode 100644 src/components/photo/HorizontalScrollableWithPhotoSection.tsx create mode 100644 src/components/photo/PhotoSection.tsx create mode 100644 src/pages/photos.tsx diff --git a/next.config.js b/next.config.js index d27ee1af9..7af7bc081 100644 --- a/next.config.js +++ b/next.config.js @@ -192,16 +192,6 @@ module.exports = { destination: '/byte-converter', permanent: false, }, - { - source: '/photos', - destination: '/drive', - permanent: false, - }, - { - source: '/:lang/photos', - destination: '/:lang/drive', - permanent: false, - }, { source: '/coingate', destination: '/redeem/coingate', diff --git a/src/assets/lang/en/photo.json b/src/assets/lang/en/photo.json new file mode 100644 index 000000000..a93418476 --- /dev/null +++ b/src/assets/lang/en/photo.json @@ -0,0 +1,164 @@ +{ + "HeroSection": { + "label": "Internxt Photos", + "title": "Secure and Encrypted Photo Storage", + "description": "Securely store, sync, backup, and access your photos in total privacy.", + "cta": "Get Internxt", + "garantee": "Included with Ultimate plans" + }, + "PhotoSection": { + "title": "Internxt Photos", + "description": [ + "Back up and sync your memories with our secure cloud storage for photos, protecting your memories with post-quantum encryption and built for complete privacy." + ] + }, + "CoreFeatures": { + "title": "Private photo storage: Your memories, accessible from anywhere", + "description": "Keep your photo library up to date and synced across all devices. Even if your device is lost or replaced, your photo library remains safe and accessible.", + "accordionCards": { + "titles": [ + "Back up of all your devices", + "Zero trackers, ads, and AI", + "Automatic syncing and backups" + ], + "descriptions": [ + "Automatically back up photos from all your devices and access them securely whenever you need them from any of your devices. ", + "Internxt Photos offers a completely ad-free experience, free from data tracking and AI using your photos and data for its machine learning.", + "Your entire photo library is continuously updated in real time, so any change you make on one device is reflected everywhere on all devices. Automatic backups mean you never have to worry about data loss." + ] + } + }, + "PhotoFeatureSection": { + "title": "Secure cloud storage for photos, focused on privacy", + "description": "Internxt Photos uses post-quantum and zero-knowledge encryption to encrypt your files and data at rest and in transit.", + "scrollableSection": { + "titles": [ + "Fully encrypted vault", + "Privacy over profit", + "Compliance", + "Open source and audited", + "Built for freedom", + "No backdoors" + ], + "descriptions": [ + "Internxt Photos uses Kyber 512 post-quantum and AES-256 encryption, ensuring data protection with end-to-end security, European privacy, and advanced technology.", + "Unlike Google Photos or other Big Tech companies, Internxt can’t view or delete your photos. Everything is encrypted directly on your device. Nobody, not even us, can access your photos or data.", + "Internxt is GDPR, HIPAA, ISO 27001, and SOC 2 compliant. Whether you need cloud storage for healthcare, finance, or personal use, Internxt ensures your data and files meet the highest standards of data protection.", + "All our code is publicly available on GitHub and has been verified by external cybersecurity experts to ensure it is a safe platform for your data.", + "We give you total ownership and control over your privacy. With our privacy-focused products, you hold the power, not Big Tech.", + "We have no access to your data or files and don’t share it with anyone. Our apps are fully open source and audited, guaranteeing your privacy is respected." + ] + } + }, + "CtaSectionV2": { + "title": "Secure cloud storage for photos", + "description": "Get the best protection and security for your memories with Internxt Photos", + "cta": "Get Internxt" + }, + "DesignedSection": { + "title": "Core features of our secure cloud storage for photos", + "scrollableSection": { + "images": ["partial1", "partial2", "partial3", "partial4"], + "titles": ["A fully private ecosystem", "Sync and backups", "End-to-end encrypted", "Encrypted backups"], + "descriptions": [ + "Internxt Photos is included with our Ultimate package, giving you everything you need to protect your privacy. From encrypted cloud storage, VPN, Mail, Antivirus, Meet, and more.", + "Photos are automatically synced and backed up in the cloud, keeping your memories safe, accessible, and protected.", + "All photos are encrypted with post-quantum and zero-knowledge encryption. Nobody but you, or those you choose to share files with, can see its contents.", + "Ensure your photos stay safe from ransomware and accidental loss with automatic, encrypted backups that provide extra protection and peace of mind." + ] + } + }, + "MadeInEuropeSection": { + "title": "Made in Europe", + "description": "Your data is protected with strict European compliance laws and regulations combined with award-winning encryption.", + "cards": { + "images": ["/images/drive/GDPR.webp", "/images/drive/redundancy.webp", "/images/drive/sustainability.webp"], + "imagesMobile": [ + "/images/drive/GDPR2.webp", + "/images/drive/Redundancy2.webp", + "/images/drive/Sustainability2.webp" + ], + "titles": ["European data centers", "Redundancy", "Commitment to sustainability "], + "descriptions": [ + "Internxt is based in Valencia, Spain, and our data centers are based in Europe and other locations worldwide, offering you security, speed, and compliance.", + "Your files are protected through redundant systems, environmental controls, and 24/7 onsite security monitoring. No matter what happens, your files always remain safe and accessible.", + "Internxt leads by example in working for a more sustainable cloud storage solution through energy-efficient practices for a cleaner and more sustainable cloud storage solution." + ] + } + }, + "TableSection": { + "title": "Internxt Plans", + "valentinesTitle": "Secure love - ", + "hotLabel": "{{discount}}% off", + "planTitles": { + "header": "Select your plan", + "lifetimeHeader": "Select your lifetime plan", + "individuals": "Annual Plans", + "homePage": "Choose the right plan for you", + "lifetime": "Lifetime Plans", + "business": "Business Plans", + "lifetimeCampaign": { + "blueText": "50% off", + "normalText": "on all our exclusive lifetime plans" + } + }, + "lifetimeDescription": "Pay once and enjoy privacy and security for life", + "planDescription": "All Internxt plans are fully featured with complete access to our award-winning privacy suite consisting of Internxt Drive, Send, VPN, Antivirus, Cleaner, Dark Web Monitor, Meet and Mail.", + "businessDescription": "We're currently working on new Internxt plans designed exclusively to defend your business, protect your employees, and secure your profitability.", + "businessDescription2": "Ensure your company's data security with Internxt's encrypted cloud storage plans.", + "billingFrequency": { + "monthly": "Monthly", + "annually": "Annual", + "lifetime": "Lifetime", + "individual": "Individual", + "business": "Business" + }, + "freePlanCard": { + "eyeBrow": "Free", + "description": "Get 1 GB of encrypted cloud storage for free", + "cta": "Select plan" + }, + "features": { + "endToEnd": "End-to-end encryption", + "openSource": "Open source and audited", + "anonymousAccount": "Anonymous account creation", + "premiumSupport": "Premium customer support", + "guarantee": "30-day money-back guarantee" + }, + "planStorage": { + "essential": "1TB", + "premium": "3TB", + "ultimate": "5TB" + } + }, + "SemanticAccordion": { + "title": "Have any questions about Internxt Photos? We have answers", + "items": [ + { + "question": "What is Internxt Photos?", + "answer": [ + "Internxt Photos is an end-to-end encrypted cloud storage for photos app, allowing you to store, sync, and automatically back up your photos and albums in total privacy." + ] + }, + { + "question": "What plan includes Internxt Photos?", + "answer": [ + "Internxt Photos is included for all Ultimate annual and lifetime plans. This also gives you access to 5TB of cloud storage and access to our full privacy suite." + ] + }, + { + "question": "Why should I use encrypted photo storage?", + "answer": [ + "Encrypted photo storage matters because most mainstream cloud photo services are not fully private. Big tech companies can view your private photos and albums in certain situations, such as moderation, AI processing, or legal requests, depending on how the service is designed." + ] + }, + { + "question": "Is Internxt Photos an alternative to Google Photos?", + "answer": [ + "Yes, Internxt Photos is a private and secure alternative to Google Photos that protects your photos with zero-knowledge and post-quantum encryption so nobody can access your files except you. ", + "With Internxt Photos, you also get access to Internxt’s full product suite which includes Drive, VPN, Meet, Mail, and more." + ] + } + ] + } +} \ No newline at end of file diff --git a/src/assets/types/photos.ts b/src/assets/types/photos.ts new file mode 100644 index 000000000..c8b415280 --- /dev/null +++ b/src/assets/types/photos.ts @@ -0,0 +1,152 @@ +export interface PhotoText { + HeroSection: HeroSection; + PhotoSection: PhotoSection; + CtaSectionV2: CtaSection; + PhotoFeatureSection: PhotoFeatureSection; + CoreFeatures: CoreFeatures; + DesignedSection: DesignedSection; + SemanticAccordion: SemanticAccordion; + MadeInEuropeSection: MadeInEuropeSection; + RelationalLinksText: RelationalLinksText; + TableSection: TableSection; + +} + +export interface HeroSection { + label: string; + title: string; + description: string; + cta: string; + garantee: string; +} + +export interface PhotoSection { + title: string; + description: string[]; +} + +export interface CtaSection { + title: string; + description: string; + cta: string; +} + +export interface PhotoFeatureSection { + title: string; + description: string; + scrollableSection: AccordionCards; +} + +export interface CoreFeatures { + title: string; + description: string; + accordionCards: AccordionCards; + cards: AccordionCards; +} + +export interface DesignedSection { + title: string; + scrollableSection: ScrollableSection; +} + +export interface ScrollableSection { + images: string[]; + titles: string[]; + descriptions: string[]; +} + +export interface AccordionCards { + titles: string[]; + descriptions: string[]; +} + +export interface SemanticAccordion { + title: string; + items: Item[]; +} + +export interface Item { + question: string; + answer: string[]; +} + +export interface MadeInEuropeSection { + title: string; + description: string; + cards: Cards; +} + +export interface Cards { + images: string[]; + imagesMobile: string[]; + titles: string[]; + descriptions: string[]; + cta: string[]; + links: string[]; +} + +export interface RelationalLinksText { + title: string; + links: Link[]; +} + +export interface Link { + cta: string; + link: string; +} + +export interface TableSection { + title: string; + hotLabel: string; + planTitles: PlanTitles; + lifetimeDescription: string; + planDescription: string; + businessDescription: string; + businessDescription2: string; + billingFrequency: BillingFrequency; + freePlanCard: FreePlanCard; + features: Features; + planStorage: PlanStorage; +} + +export interface BillingFrequency { + monthly: string; + annually: string; + lifetime: string; + individual: string; + business: string; +} + +export interface FreePlanCard { + eyeBrow: string; + description: string; + cta: string; +} + +export interface PlanStorage { + essential: string; + premium: string; + ultimate: string; +} + +export interface PlanTitles { + header: string; + individuals: string; + homePage: string; + lifetime: string; + business: string; + lifetimeCampaign: LifetimeCampaign; +} + +export interface LifetimeCampaign { + blueText: string; + normalText: string; +} + +export interface Features { + endToEnd: string; + openSource: string; + anonymousAccount: string; + premiumSupport: string; + guarantee: string; +} \ No newline at end of file diff --git a/src/components/photo/CoreFeaturesSection.tsx b/src/components/photo/CoreFeaturesSection.tsx new file mode 100644 index 000000000..47dabc45b --- /dev/null +++ b/src/components/photo/CoreFeaturesSection.tsx @@ -0,0 +1,111 @@ +import { PhotoText } from '@/assets/types/photos'; +import { useState, useRef, useEffect } from 'react'; +import Image from 'next/image'; +import { getImage } from '@/lib/getImage'; + +interface CoreFeaturesSectionProps { + textContent: PhotoText['CoreFeatures']; +} + +const CoreFeaturesSection = ({ textContent }: CoreFeaturesSectionProps): JSX.Element => { + const [activeIndex, setActiveIndex] = useState(0); + const scrollContainerRef = useRef(null); + const [isMobile, setIsMobile] = useState(false); + + const mobileCardWidth = 293; + + useEffect(() => { + const checkIsMobile = () => { + setIsMobile(window.innerWidth < 1024); + }; + + checkIsMobile(); + window.addEventListener('resize', checkIsMobile); + + return () => window.removeEventListener('resize', checkIsMobile); + }, []); + + const handleAccordionClick = (index: number) => { + setActiveIndex(index); + }; + + const getPaddingRight = () => { + const containerWidth = 345; + const visibleWidth = mobileCardWidth; + const paddingRight = containerWidth - visibleWidth; + return paddingRight; + }; + + return ( +
+
+

{textContent.title}

+

{textContent.description}

+
+
+
+ mobile-features +
+ {textContent.accordionCards.titles.map((title: string, index: number) => ( + + ))} +
+
+
+ {textContent.accordionCards.titles[activeIndex]} +
+
+
+ +
+
+
+
+
+
+ ); +}; + +export default CoreFeaturesSection; \ No newline at end of file diff --git a/src/components/photo/HeroSection.tsx b/src/components/photo/HeroSection.tsx new file mode 100644 index 000000000..caadb1e0c --- /dev/null +++ b/src/components/photo/HeroSection.tsx @@ -0,0 +1,53 @@ +import Link from 'next/link'; +import { CheckCircle } from '@phosphor-icons/react'; +import { getImage } from '@/lib/getImage'; +import Image from 'next/image'; + +export const HeroSection = ({ textContent }) => { + + return ( +
+
+
+

{textContent.label}

+
+

+ {textContent.title} +

+

{textContent.description}

+
+ + {textContent.cta} + +
+ +

+ {textContent.garantee} +

+
+
+
+
+ Internxt Drive panel interface +
+
+ ); +}; diff --git a/src/components/photo/HorizontalScrollableSection.tsx b/src/components/photo/HorizontalScrollableSection.tsx new file mode 100644 index 000000000..17ce692c7 --- /dev/null +++ b/src/components/photo/HorizontalScrollableSection.tsx @@ -0,0 +1,186 @@ +import { CaretLeft, CaretRight } from '@phosphor-icons/react'; +import { useRef, useState, useEffect } from 'react'; + +interface HorizontalScrollableProps { + textContent: any; + bgGradient?: string; + cardsWidth?: string; + cardsHeight?: string; + needsDivider?: boolean; + needsH2?: boolean; + needsH3?: boolean; +} + +export default function HorizontalScrollableSection({ + textContent, + bgGradient, + cardsWidth = '400px', + cardsHeight = 'auto', + needsDivider = true, + needsH2 = false, + needsH3 = false, +}: Readonly): JSX.Element { + const scrollContainerRef = useRef(null); + const [canScrollLeft, setCanScrollLeft] = useState(false); + const [canScrollRight, setCanScrollRight] = useState(true); + const [isMobile, setIsMobile] = useState(false); + + const mobileCardWidth = 293; + const mobileGap = 32; + const desktopGap = 32; + + useEffect(() => { + const checkIsMobile = () => { + setIsMobile(window.innerWidth < 1024); + }; + + checkIsMobile(); + window.addEventListener('resize', checkIsMobile); + + return () => window.removeEventListener('resize', checkIsMobile); + }, []); + + const getScrollAmount = () => { + if (isMobile) { + return mobileCardWidth + mobileGap; + } + const cardWidthNum = Number.parseInt(cardsWidth); + return cardWidthNum + desktopGap; + }; + + const getPaddingRight = () => { + if (isMobile) { + const containerWidth = 345; + const visibleWidth = mobileCardWidth; + const paddingRight = containerWidth - visibleWidth; + return paddingRight; + } + + const cardWidthNum = Number.parseInt(cardsWidth); + const containerWidth = 850; + const visibleWidth = 2 * cardWidthNum + desktopGap; + const paddingRight = containerWidth - visibleWidth; + return paddingRight; + }; + + const updateScrollButtons = () => { + if (!scrollContainerRef.current) return; + + const { scrollLeft, scrollWidth, clientWidth } = scrollContainerRef.current; + + setCanScrollLeft(scrollLeft > 0); + setCanScrollRight(scrollLeft < scrollWidth - clientWidth - 1); + }; + + const scrollLeft = () => { + if (!scrollContainerRef.current) return; + const scrollAmount = getScrollAmount(); + scrollContainerRef.current.scrollBy({ + left: -scrollAmount, + behavior: 'smooth', + }); + }; + + const scrollRight = () => { + if (!scrollContainerRef.current) return; + const scrollAmount = getScrollAmount(); + scrollContainerRef.current.scrollBy({ + left: scrollAmount, + behavior: 'smooth', + }); + }; + + useEffect(() => { + const scrollContainer = scrollContainerRef.current; + if (!scrollContainer) return; + updateScrollButtons(); + scrollContainer.addEventListener('scroll', updateScrollButtons); + const resizeObserver = new ResizeObserver(updateScrollButtons); + resizeObserver.observe(scrollContainer); + return () => { + scrollContainer.removeEventListener('scroll', updateScrollButtons); + resizeObserver.disconnect(); + }; + }, [isMobile]); + + const TitleTag = needsH2 ? 'h2' : 'p'; + const CardTag = needsH3 ? 'h3' : 'p'; + return ( +
+
+ {needsDivider && ( +
+ )} + + {textContent.title} + +

{textContent.description}

+
+ +
+
+ {textContent.scrollableSection.titles.map((title: string, index: number) => ( +
+
+ {title} +

+ {textContent.scrollableSection.descriptions[index]} +

+
+
+ ))} +
+ +
+
+ + +
+
+
+
+ ); +} diff --git a/src/components/photo/HorizontalScrollableWithPhotoSection.tsx b/src/components/photo/HorizontalScrollableWithPhotoSection.tsx new file mode 100644 index 000000000..c7e6aeacc --- /dev/null +++ b/src/components/photo/HorizontalScrollableWithPhotoSection.tsx @@ -0,0 +1,176 @@ +import { getImage } from '@/lib/getImage'; +import { CaretLeft, CaretRight } from '@phosphor-icons/react'; +import { useRef, useState, useEffect } from 'react'; +import Image from 'next/image'; +import { PhotoText } from '@/assets/types/photos'; + +interface HorizontalScrollableSectionWithPhotosProps { + textContent: PhotoText['DesignedSection']; + needsDivider?: boolean; +} + +export default function HorizontalScrollableSectionWithPhotosSection({ + textContent, + needsDivider = true, +}: Readonly): JSX.Element { + const cardTitles = textContent?.scrollableSection.titles ?? []; + const cardDescriptions = textContent?.scrollableSection.descriptions ?? []; + const scrollContainerRef = useRef(null); + const [currentIndex, setCurrentIndex] = useState(0); + const [isMobile, setIsMobile] = useState(false); + + const cardWidth = 350; + const mobileCardWidth = 300; + const gap = 24; + const scrollAmount = cardWidth + gap; + const mobileScrollAmount = mobileCardWidth + gap; + + useEffect(() => { + const checkIsMobile = () => { + setIsMobile(window.innerWidth < 1024); + }; + + checkIsMobile(); + window.addEventListener('resize', checkIsMobile); + + return () => window.removeEventListener('resize', checkIsMobile); + }, []); + + const getMaxIndex = () => { + if (isMobile) { + return Math.max(0, cardTitles.length - 1); + } else { + return Math.max(0, cardTitles.length - 2); + } + }; + + const handleScroll = () => { + if (scrollContainerRef.current) { + const scrollLeft = scrollContainerRef.current.scrollLeft; + const amount = isMobile ? mobileScrollAmount : scrollAmount; + const newIndex = Math.round(scrollLeft / amount); + setCurrentIndex(Math.min(newIndex, getMaxIndex())); + } + }; + + const scrollLeft = () => { + if (currentIndex > 0 && scrollContainerRef.current) { + const newIndex = currentIndex - 1; + setCurrentIndex(newIndex); + const amount = isMobile ? mobileScrollAmount : scrollAmount; + const element = scrollContainerRef.current; + if (element && 'scrollTo' in element) { + element.scrollTo({ + left: newIndex * amount, + behavior: 'smooth', + }); + } + } + }; + + const scrollRight = () => { + const maxIndex = getMaxIndex(); + if (currentIndex < maxIndex && scrollContainerRef.current) { + const newIndex = currentIndex + 1; + setCurrentIndex(newIndex); + const amount = isMobile ? mobileScrollAmount : scrollAmount; + const element = scrollContainerRef.current; + if (element && 'scrollTo' in element) { + element.scrollTo({ + left: newIndex * amount, + behavior: 'smooth', + }); + } + } + }; + + const maxIndex = getMaxIndex(); + + return ( +
+
+
+

{textContent.title}

+
+
+ +
+
+
+ {cardTitles.map((title: string, index: number) => ( +
+
+

{title}

+

{cardDescriptions[index]}

+
+ Internxt Mail + Internxt Mail +
+ ))} +
+
+
+
+ + +
+
+
+
+ ); +} diff --git a/src/components/photo/PhotoSection.tsx b/src/components/photo/PhotoSection.tsx new file mode 100644 index 000000000..55f41cf80 --- /dev/null +++ b/src/components/photo/PhotoSection.tsx @@ -0,0 +1,43 @@ +import { getImage } from '@/lib/getImage'; +import Image from 'next/image'; + +interface PhotoSectionProps { + textContent: { + title: string; + description: string[]; + }; +} + +const PhotoSection = ({ textContent }: PhotoSectionProps): JSX.Element => { + return ( +
+

{textContent.title}

+ +
+
+ {textContent.description.map((desc) => ( +

+ {desc} +

+ ))} +
+ +
+ Internxt Drive panel interface +
+
+
+
+
+
+ ); +}; + +export default PhotoSection; \ No newline at end of file diff --git a/src/pages/photos.tsx b/src/pages/photos.tsx new file mode 100644 index 000000000..9d7cd158c --- /dev/null +++ b/src/pages/photos.tsx @@ -0,0 +1,175 @@ +import { FooterText, MetatagsDescription, NavigationBarText } from '@/assets/types/layout/types'; +import { GetServerSidePropsContext } from 'next'; +import Footer from '@/components/layout/footers/Footer'; +import Layout from '@/components/layout/Layout'; +import Navbar from '@/components/layout/navbars/Navbar'; +import FAQSection from "@/components/shared/sections/FaqSection"; +import { PhotoText, RelationalLinksText } from '@/assets/types/photos'; +import HorizontalScrollableSection from '@/components/photo/HorizontalScrollableSection'; +import { HeroSection } from '@/components/photo/HeroSection'; +import PhotoSection from '@/components/photo/PhotoSection'; +import CoreFeaturesSection from '@/components/photo/CoreFeaturesSection'; +import FloatingCtaSectionv2 from '@/components/shared/FloatingCtaSectionV2'; +import ThreeCardsSection from '@/components/shared/sections/ThreeCardsWithImagesSection'; +import HorizontalScrollableWithPhotoSection from '@/components/photo/HorizontalScrollableWithPhotoSection' +import RelationalLinks from '@/components/shared/sections/RelationalLinks'; +import { PricingSectionWrapper } from '@/components/shared/pricing/PricingSectionWrapper'; +import usePricing from '@/hooks/usePricing'; +import { PromoCodeName } from '@/lib/types'; +import { stripeService } from '@/services/stripe.service'; + +interface PhotoProps { + metatagsDescription: MetatagsDescription[]; + navbarText: NavigationBarText; + textContent: PhotoText; + footerText: FooterText; + locale: GetServerSidePropsContext['locale']; + relationalLinksText: RelationalLinksText; +} + +const Photo = ({ + metatagsDescription, + navbarText, + footerText, + textContent, + locale, + relationalLinksText, +} : PhotoProps): JSX.Element => { + const metatags = metatagsDescription.find((metatag) => metatag.id === 'photo'); + const lang = locale as string; + const { + products, + loadingCards, + currencyValue, + coupon: individualCoupon, + lifetimeCoupon: lifetimeCoupon, + lifetimeCoupons, + } = usePricing({ couponCode: PromoCodeName.OFFSUB, couponCodeForLifetime: PromoCodeName.OFFLFT }); + + const decimalDiscountForLifetime = lifetimeCoupon?.percentOff && 100 - lifetimeCoupon.percentOff; + const decimalDiscount = individualCoupon?.percentOff && 100 - individualCoupon.percentOff; + + const onCheckoutButtonClicked = async ( + priceId: string, + isCheckoutForLifetime: boolean, + interval: string, + storage: string, + ) => { + const couponCodeForCheckout = isCheckoutForLifetime ? lifetimeCoupon : individualCoupon; + + const finalPrice = await stripeService.calculateFinalPrice( + priceId, + interval, + currencyValue, + 'individuals', + couponCodeForCheckout, + ); + + stripeService.redirectToCheckout( + priceId, + finalPrice, + currencyValue, + 'individual', + isCheckoutForLifetime, + interval, + storage, + couponCodeForCheckout?.name, + ); + }; + return ( + + + + + + + + + + + + + + + + + + +

{textContent.CtaSectionV2.title}

+

{textContent.CtaSectionV2.description}

+ + } + url="/pricing" + bgGradientColor="#FFFFFF 100%" + bgGradientContainerColor="background: linear-gradient(115.95deg, rgba(244, 248, 255, 0.75) 10.92%, rgba(255, 255, 255, 0.08) 96.4%)" + containerDetails="shadow-[0_4px_20px_0_rgba(0,0,0,0.1)] backdrop-blur-[55px]" + bgPadding="lg:py-20 lg:pb-20 pb-10" + /> + +
+
+
+ + + + + +