From a0b09bf34d51003d9df1e8c1a26b74996472b13b Mon Sep 17 00:00:00 2001 From: Joel DSouza Date: Tue, 7 Nov 2023 10:50:13 +0100 Subject: [PATCH] refactor(promotion banner): added promotion banner in articles --- apps/research/public/aoc_settings.svg | 77 ++++++ apps/research/public/nn_settings.svg | 257 ++++++++++++++++++ apps/research/public/pro_product.svg | 148 +++++----- apps/research/public/pro_settings.svg | 185 +++++++++++++ apps/research/public/twic_settings.svg | 99 +++++++ .../src/components/article/Article.tsx | 1 + .../article-actions/StartTrialCall.tsx | 4 +- .../article/article-body/PrivateArticle.tsx | 46 ++-- .../article/article-header/ArticleHeader.tsx | 93 ++++--- .../components/platform/BottomPromotion.tsx | 47 ++++ .../src/components/platform/PricingCard.tsx | 2 +- .../src/components/platform/PricingTable.tsx | 48 +++- .../src/components/platform/TopPromotion.tsx | 47 ++++ .../research/src/components/platform/index.ts | 2 + .../components/platform/styles.module.scss | 77 ++++++ .../settings/emails/EmailSetting.tsx | 2 +- .../components/settings/payments/Payments.tsx | 10 +- apps/research/src/config/index.ts | 8 +- apps/research/src/hooks/index.ts | 2 +- .../src/pages/articles/[slug]/index.tsx | 7 +- apps/research/src/pages/index.tsx | 38 +-- apps/research/src/types/ui.ts | 1 + 22 files changed, 1024 insertions(+), 177 deletions(-) create mode 100644 apps/research/public/aoc_settings.svg create mode 100644 apps/research/public/nn_settings.svg create mode 100644 apps/research/public/pro_settings.svg create mode 100644 apps/research/public/twic_settings.svg create mode 100644 apps/research/src/components/platform/BottomPromotion.tsx create mode 100644 apps/research/src/components/platform/TopPromotion.tsx diff --git a/apps/research/public/aoc_settings.svg b/apps/research/public/aoc_settings.svg new file mode 100644 index 00000000..35f44aa1 --- /dev/null +++ b/apps/research/public/aoc_settings.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/research/public/nn_settings.svg b/apps/research/public/nn_settings.svg new file mode 100644 index 00000000..8f7d105b --- /dev/null +++ b/apps/research/public/nn_settings.svg @@ -0,0 +1,257 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/research/public/pro_product.svg b/apps/research/public/pro_product.svg index 3128b71a..c0bac245 100644 --- a/apps/research/public/pro_product.svg +++ b/apps/research/public/pro_product.svg @@ -114,77 +114,78 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -214,7 +215,10 @@ - + + + + diff --git a/apps/research/public/pro_settings.svg b/apps/research/public/pro_settings.svg new file mode 100644 index 00000000..1dc8803d --- /dev/null +++ b/apps/research/public/pro_settings.svg @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/research/public/twic_settings.svg b/apps/research/public/twic_settings.svg new file mode 100644 index 00000000..67be74e6 --- /dev/null +++ b/apps/research/public/twic_settings.svg @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/research/src/components/article/Article.tsx b/apps/research/src/components/article/Article.tsx index 616780b9..a481d316 100644 --- a/apps/research/src/components/article/Article.tsx +++ b/apps/research/src/components/article/Article.tsx @@ -10,6 +10,7 @@ import { ArticleRecommendations, } from './article-body'; import { Divider } from 'antd'; +import { TopPromotion } from '../platform'; interface ArticleProps extends Omit< diff --git a/apps/research/src/components/article/article-actions/StartTrialCall.tsx b/apps/research/src/components/article/article-actions/StartTrialCall.tsx index ed1541e2..8d1988aa 100644 --- a/apps/research/src/components/article/article-actions/StartTrialCall.tsx +++ b/apps/research/src/components/article/article-actions/StartTrialCall.tsx @@ -65,7 +65,7 @@ const StartTrialCall: React.FC = ({ handleYearlyCheckout={isLoggedOut ? signIn : yearlyCheckout} isLoading={isLoading} handleCheckout={isLoggedOut ? signIn : checkout} - label="Start 30-Day Free Trial" + label="Subscribe Now" isFreeTrial features={appStructure.payments[productKeys].features} yearlyPrice={appStructure.payments[productKeys].yearlyPrice} @@ -76,7 +76,7 @@ const StartTrialCall: React.FC = ({ Already subscribed?{' '} diff --git a/apps/research/src/components/article/article-body/PrivateArticle.tsx b/apps/research/src/components/article/article-body/PrivateArticle.tsx index 597d3a85..ddcc4ad5 100644 --- a/apps/research/src/components/article/article-body/PrivateArticle.tsx +++ b/apps/research/src/components/article/article-body/PrivateArticle.tsx @@ -12,6 +12,7 @@ import { useCustomerCheckout, useProductInfo } from '@/hooks'; import { motion } from 'framer-motion'; import { appStructure } from '@/config'; import { getProductSection, sectionKeys } from '@/utils'; +import { TopPromotion } from '@/components'; interface PrivateArticleProps extends React.PropsWithChildren, @@ -102,11 +103,14 @@ const PrivateArticle: React.FC = ({ case 'blocked': return ; case 'ended': + if (completePackageStatus.state === 'ended') { + return null; + } return ( ); @@ -148,19 +152,29 @@ const PrivateArticle: React.FC = ({ ); return ( - - - {getCallToAction(productStatus.state)} - - + <> + + + {completePackageStatus.state === 'ended' && ( + + )} + {getCallToAction(productStatus.state)} + + + ); }; diff --git a/apps/research/src/components/article/article-header/ArticleHeader.tsx b/apps/research/src/components/article/article-header/ArticleHeader.tsx index 66323390..e026d8d8 100644 --- a/apps/research/src/components/article/article-header/ArticleHeader.tsx +++ b/apps/research/src/components/article/article-header/ArticleHeader.tsx @@ -7,6 +7,7 @@ import ArticleMetaData from '../ArticleMetaData'; import { downloadResource, getProductSection, sectionKeys } from '@/utils'; import { useProductInfo } from '@/hooks'; import { appStructure } from '@/config'; +import { TopPromotion } from '@/components'; const { Title, Text } = Typography; const { useToken } = theme; @@ -22,47 +23,77 @@ const ArticleHeader: React.FC = ({ reportDocument, ...metadata }) => { - const { productStatus, appState } = useProductInfo( + const { productStatus: proProductStatus, appState } = useProductInfo( appStructure.payments.pro.productId ); const { token: { fontSizeSM }, } = useToken(); - const productSection = getProductSection(metadata.sectionsCollection); - const productKey = sectionKeys[productSection?.name!] ?? 'pro'; + const productSection = + getProductSection(metadata.sectionsCollection)?.name ?? ''; + const productKey = sectionKeys[productSection] ?? 'pro'; + + const { productStatus } = useProductInfo( + appStructure.payments[productKey].productId + ); + + React.useEffect(() => { + console.log( + !['active', 'ended', 'blocked'].includes(productStatus.state ?? 'loading') + ); + console.log( + !['active', 'ended', 'blocked'].includes( + proProductStatus.state ?? 'loading' + ) + ); + }, [proProductStatus, productStatus]); return ( -
- - - {title} - - {subtitle} -
- {image.title - {image.description && ( - - {image.description} - + <> + {!['active', 'ended', 'blocked'].includes( + productStatus.state ?? 'loading' + ) && + !['active', 'ended', 'blocked'].includes( + proProductStatus.state ?? 'loading' + ) && } +
+ + + {title} + + {subtitle} +
+ {image.title + {image.description && ( + + {image.description} + + )} +
+ {reportDocument && ( + <> + {(productStatus.state === 'active' || + proProductStatus.state === 'active' || + productKey === 'pro') && ( + + )} + )} +
- {reportDocument && ( - <> - {(productStatus.state === 'active' || productKey === 'pro') && ( - - )} - - )} - -
+ ); }; diff --git a/apps/research/src/components/platform/BottomPromotion.tsx b/apps/research/src/components/platform/BottomPromotion.tsx new file mode 100644 index 00000000..a1e07b94 --- /dev/null +++ b/apps/research/src/components/platform/BottomPromotion.tsx @@ -0,0 +1,47 @@ +import * as React from 'react'; + +import { Button, Divider, Grid, Image, Typography } from 'antd'; +import styles from './styles.module.scss'; +import Link from 'next/link'; + +const BottomPromotion: React.FC = () => { + const { lg, xl } = Grid.useBreakpoint(); + return ( + +
+
+ + Digital Assets Research + + + For Professional and Institutional Investors + +
+ + + +
+
+ + ); +}; + +export default BottomPromotion; diff --git a/apps/research/src/components/platform/PricingCard.tsx b/apps/research/src/components/platform/PricingCard.tsx index e4f33818..36d9790b 100644 --- a/apps/research/src/components/platform/PricingCard.tsx +++ b/apps/research/src/components/platform/PricingCard.tsx @@ -32,7 +32,7 @@ const PricingCard: React.FC = ({ token: { colorInfo }, } = theme.useToken(); return ( - + { useCustomerCheckout(appStructure.payments.twic.annualPriceId); React.useEffect(() => { + if (!router.query.redirectUrl || !router.query.plan || !router.query.type) + return; + const { plan, redirectUrl, type } = router.query; if (productStatus.state == 'active') { - router.push((redirectUrl as string) ?? '/home'); + router.push(redirectUrl as string); } else if (aocProductStatus.state === 'active' && plan === 'aoc') { - router.push((redirectUrl as string) ?? '/home'); + router.push(redirectUrl as string); } else if (nnProductStatus.state === 'active' && plan === 'nn') { - router.push((redirectUrl as string) ?? '/home'); + router.push(redirectUrl as string); } else if (twicProductStatus.state === 'active' && plan === 'twic') { - router.push((redirectUrl as string) ?? '/home'); + router.push(redirectUrl as string); } else if (plan === 'pro') { if (type === 'monthly') { monthlyCheckOut(); @@ -295,7 +298,7 @@ const PricingTable = () => { case 'pro': return ( { plan="pro" url={router.query.redirectUrl as string} type="monthly" + trial /> ) : ( getMonthlyActions( @@ -324,6 +328,7 @@ const PricingTable = () => { undefined, undefined, undefined, + true, true ) )} @@ -334,7 +339,7 @@ const PricingTable = () => { default: return ( { plan="pro" url={router.query.redirectUrl as string} type="monthly" + trial /> ) : ( getMonthlyActions( @@ -363,6 +369,7 @@ const PricingTable = () => { undefined, undefined, undefined, + true, true ) )} @@ -492,7 +499,7 @@ const PricingTable = () => { isYear description={payments.description} image={payments.image} - badge={'SAVE $140 on yearly Subscription!'} + badge={'SAVE $140 by combining all subscriptions!'} {...(productStatus.priceId === annualPriceId && productStatus.state === 'blocked' && { state: 'blocked', @@ -511,6 +518,7 @@ const PricingTable = () => { plan="pro" url={router.query.redirectUrl as string} type="year" + trial /> ) : ( getAnnualActions( @@ -519,6 +527,7 @@ const PricingTable = () => { undefined, undefined, undefined, + true, true ) )} @@ -530,7 +539,7 @@ const PricingTable = () => { return ( { plan="pro" url={router.query.redirectUrl as string} type="year" + trial /> ) : ( getAnnualActions( @@ -559,6 +569,7 @@ const PricingTable = () => { undefined, undefined, undefined, + true, true ) )} @@ -714,7 +725,7 @@ interface LogoutActionButtonProps { } const LogoutActionButton: React.FC = ({ - url, + url = window.location.href, badge = false, plan = 'pro', type = 'year', @@ -723,13 +734,26 @@ const LogoutActionButton: React.FC = ({ const { token: { colorInfo }, } = useToken(); + + const router = useRouter(); + + const getUrl = () => { + if (router.query.redirectUrl) { + return window.location.href; + } else { + return ( + window.location.href + + '/pricing' + + '?redirectUrl=' + + window.location.href + ); + } + }; return ( { + const { lg, xl } = Grid.useBreakpoint(); + return ( + +
+
+ + Digital Assets Research + + + For Professional and Institutional Investors + +
+ + + +
+
+ + ); +}; + +export default TopPromotion; diff --git a/apps/research/src/components/platform/index.ts b/apps/research/src/components/platform/index.ts index d1eba405..60ca3f07 100644 --- a/apps/research/src/components/platform/index.ts +++ b/apps/research/src/components/platform/index.ts @@ -9,3 +9,5 @@ export * from './SectionHeader'; export { default as SectionDescriptionHeader } from './SectionDescriptionHeader'; export { default as PricingTable } from './PricingTable'; export { default as PrivateLayout } from './PrivateLayout'; +export { default as TopPromotion } from './TopPromotion'; +export { default as BottomPromotion } from './BottomPromotion'; diff --git a/apps/research/src/components/platform/styles.module.scss b/apps/research/src/components/platform/styles.module.scss index 3384add0..4d29f2e9 100644 --- a/apps/research/src/components/platform/styles.module.scss +++ b/apps/research/src/components/platform/styles.module.scss @@ -65,3 +65,80 @@ } } } + +.topPromotion { + width: 100%; + display: flex; + flex-direction: column-reverse; + transition: all; + transition-duration: 75ms; + transition-timing-function: ease-in; + > div { + width: 100%; + } + > div:first-child { + padding: 18px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 18px; + > div { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 4px; + } + } + > div:last-child { + background-image: url(/research/top_mobile_promotion.svg); + background-repeat: no-repeat; + background-size: cover; + height: 100px; + } +} + +@include medium() { + .topPromotion { + > div:last-child { + height: 140px; + } + } +} + +@include large() { + .topPromotion { + flex-direction: row; + position: relative; + > div:first-child { + padding: 18px; + align-items: start; + > div { + align-items: start; + } + } + > div:last-child { + height: auto; + background-image: url(/research/top_banner.png); + } + } +} + +@include xlarge() { + .topPromotion { + flex-direction: row; + > div:first-child { + padding: 24px; + } + } +} + +.bottomPromotion { + padding: 16px 32px; +} + +@include large() { + .bottomPromotion { + } +} diff --git a/apps/research/src/components/settings/emails/EmailSetting.tsx b/apps/research/src/components/settings/emails/EmailSetting.tsx index b92e155b..828c1035 100644 --- a/apps/research/src/components/settings/emails/EmailSetting.tsx +++ b/apps/research/src/components/settings/emails/EmailSetting.tsx @@ -98,7 +98,7 @@ const EmailSetting: React.FC = ({ )} {productStatus === null && !isPro && ( )}
diff --git a/apps/research/src/components/settings/payments/Payments.tsx b/apps/research/src/components/settings/payments/Payments.tsx index 0ad6ac82..46748961 100644 --- a/apps/research/src/components/settings/payments/Payments.tsx +++ b/apps/research/src/components/settings/payments/Payments.tsx @@ -133,6 +133,7 @@ const Payments: React.FC = () => { dashboardProps={customerDashboard} checkoutProps={proMonthlyCheckout} payment={appStructure.payments.pro} + isTrial /> ); default: @@ -147,6 +148,7 @@ const Payments: React.FC = () => { dashboardProps={customerDashboard} checkoutProps={proMonthlyCheckout} payment={appStructure.payments.pro} + isTrial /> ); } @@ -219,6 +221,7 @@ interface PlanProductProps { }; payment: Payments; isLoading: boolean; + isTrial?: boolean; } const PlanProduct: React.FC = ({ @@ -228,6 +231,7 @@ const PlanProduct: React.FC = ({ checkoutProps: { doCheckOut, isLoading: checkoutLoading }, dashboardProps: { customerDashboard, isLoading: isDashboardLoading }, payment, + isTrial = false, }) => { const { sm, xl } = Grid.useBreakpoint(); switch (productStatus) { @@ -273,7 +277,7 @@ const PlanProduct: React.FC = ({ width: !xl ? '100%' : 'null', }} > - Renew PRO Subscription + Renew Subscription ); @@ -287,7 +291,7 @@ const PlanProduct: React.FC = ({ width: !xl ? '100%' : 'null', }} > - Start 30-Day Free Trial + {isTrial ? 'Start 30-Day Free Trial' : 'Subscribe Now'} ); @@ -324,7 +328,7 @@ const PaymentCard: React.FC = ({ id="payments-card" >
- +
{sm ? ( diff --git a/apps/research/src/config/index.ts b/apps/research/src/config/index.ts index 05796d3e..21ca4452 100644 --- a/apps/research/src/config/index.ts +++ b/apps/research/src/config/index.ts @@ -114,6 +114,7 @@ export const appStructure: AppStructure = { annualPriceId: process.env.NEXT_PUBLIC_STRIPE_YEARLY_PRICE_ID_TWIC!, monthlyPriceId: process.env.NEXT_PUBLIC_STRIPE_MONTHLY_PRICE_ID_TWIC!, image: '/research/twic_product.svg', + settingsImage: '/research/twic_settings.svg', name: 'This Week in Crypto', description: 'Our carefully distilled newsletter delivered to your inbox every Friday. We take great pride in delivering you the most important news only, explaining them in plain English, and analysing the potential implications.', @@ -130,6 +131,7 @@ export const appStructure: AppStructure = { annualPriceId: process.env.NEXT_PUBLIC_STRIPE_YEARLY_PRICE_ID_NN!, monthlyPriceId: process.env.NEXT_PUBLIC_STRIPE_MONTHLY_PRICE_ID_NN!, image: '/research/nn_product.svg', + settingsImage: '/research/nn_settings.svg', name: 'Navigating Narratives', description: 'A weekly research note looking at what’s brewing in crypto and DeFi. Delivered directly to your inbox.', @@ -146,6 +148,7 @@ export const appStructure: AppStructure = { annualPriceId: process.env.NEXT_PUBLIC_STRIPE_YEARLY_PRICE_ID_AOC!, monthlyPriceId: process.env.NEXT_PUBLIC_STRIPE_MONTHLY_PRICE_ID_AOC!, image: '/research/aoc_product.svg', + settingsImage: '/research/aoc_settings.svg', name: 'Ahead of The Curve', description: 'The weekly market report with signals from the derivatives market, market structure and expert opinion. Delivered directly to your inbox.', @@ -162,9 +165,10 @@ export const appStructure: AppStructure = { annualPriceId: process.env.NEXT_PUBLIC_STRIPE_YEARLY_PRICE_ID_PRO!, monthlyPriceId: process.env.NEXT_PUBLIC_STRIPE_MONTHLY_PRICE_ID_PRO!, image: '/research/pro_product.svg', - name: 'Complete Package', + settingsImage: '/research/pro_settings.svg', + name: 'PRO - The Complete Package', description: - 'The weekly market report with signals from the derivatives market, market structure and expert opinion. Delivered directly to your inbox.', + 'Full access to all research content, including This Week in Crypto, Navigating Narratives and Ahead of the Curve.', monthlyPrice: '$70.00', yearlyPrice: '$700.00', features: [ diff --git a/apps/research/src/hooks/index.ts b/apps/research/src/hooks/index.ts index d3a1b7b7..f0463137 100644 --- a/apps/research/src/hooks/index.ts +++ b/apps/research/src/hooks/index.ts @@ -43,7 +43,7 @@ export const useCustomerCheckout = (priceId: string) => { priceId: priceId, successUrl: (router.query.redirectUrl as string) ?? window.location.href, - cancelUrl: window.location.href, + cancelUrl: (router.query.redirectUrl as string) ?? window.location.href, }).unwrap(); window.location.href = response.url; } catch (err) {} diff --git a/apps/research/src/pages/articles/[slug]/index.tsx b/apps/research/src/pages/articles/[slug]/index.tsx index 032953a0..c8b9a7eb 100644 --- a/apps/research/src/pages/articles/[slug]/index.tsx +++ b/apps/research/src/pages/articles/[slug]/index.tsx @@ -12,16 +12,19 @@ import type { import type { GetStaticPaths, GetStaticProps } from 'next'; import { Layout, Row, Col, Grid, theme } from 'antd'; import { ReactElement } from 'react'; -import { NextPageWithLayout } from 'platform-js'; +import { NextPageWithLayout, useAppState } from 'platform-js'; import { NextSeo, ArticleJsonLd } from 'next-seo'; import { Article, ArticleSidebar, ShareArticle, ArticleRecommendations, + TopPromotion, + BottomPromotion, } from '@/components'; import Head from 'next/head'; import { siteUsername } from '@/utils'; +import config from '@/firebase/config'; const { Content } = Layout; const { useBreakpoint } = Grid; @@ -51,6 +54,8 @@ const ArticlePage: NextPageWithLayout = ({ ...articleContent } = page; + const state = useAppState(config); + return ( <> diff --git a/apps/research/src/pages/index.tsx b/apps/research/src/pages/index.tsx index 409580ca..b9fe99ed 100644 --- a/apps/research/src/pages/index.tsx +++ b/apps/research/src/pages/index.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { NextPageWithLayout } from 'platform-js'; import { + BottomPromotion, DashboardList, HomeDashboard, IndustryDashboard, @@ -9,6 +10,7 @@ import { PricingTable, SimpleLayout, TokenValuationCover, + TopPromotion, } from '@/components'; import { NextSeo } from 'next-seo'; import { GetStaticProps } from 'next'; @@ -86,41 +88,7 @@ const Home: NextPageWithLayout = ({ }} />
- -
-
- - Digital Assets Research - - - For Professional and Institutional Investors - -
- - - -
- -
- +
diff --git a/apps/research/src/types/ui.ts b/apps/research/src/types/ui.ts index 319751aa..2af6dd65 100644 --- a/apps/research/src/types/ui.ts +++ b/apps/research/src/types/ui.ts @@ -11,6 +11,7 @@ export interface Payments { annualPriceId: string; monthlyPriceId: string; image: string; + settingsImage: string; name: string; description: string; monthlyPrice: string;