Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Zj cp/end lifetime sale #1492

Merged
merged 2 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,24 +1,150 @@
'use client'
import * as React from 'react'
import {FunctionComponent, useEffect} from 'react'
import {useViewer} from '@/context/viewer-context'
import {redirectToStandardCheckout} from '@/api/stripe/stripe-checkout-redirect'
import React from 'react'
import emailIsValid from '@/utils/email-is-valid'
import {track} from '@/utils/analytics'
import {useRouter, useSearchParams} from 'next/navigation'
import PoweredByStripe from '@/components/pricing/powered-by-stripe'
import Spinner from '../spinner'
import {useRouter} from 'next/navigation'
import {useViewer} from '@/context/viewer-context'
import PoweredByStripe from '../powered-by-stripe'
import {twMerge} from 'tailwind-merge'
import slugify from 'slugify'
import Spinner from '@/components/spinner'

// TODO: Extract PlanTitle and PlanPrice to shared components.
const ActiveSale = ({lastCharge}: {lastCharge: {amountPaid: number}}) => {
const {viewer} = useViewer()

const PlanTitle: React.FunctionComponent<React.PropsWithChildren<unknown>> = ({
children,
}) => (
<h2 className="text-xl font-bold text-gray-900 dark:text-white">
{children}
</h2>
)
const router = useRouter()

const [loaderOn, setLoaderOn] = React.useState<boolean>(false)

const priceId = process.env.NEXT_PUBLIC_STRIPE_LIFETIME_MEMBERSHIP_PRICE_ID
const quantity = 1
const pricesLoading = false

const amountPaid =
lastCharge?.amountPaid === 0 ? null : lastCharge?.amountPaid

const onClickCheckout = async () => {
if (!priceId) return

if (emailIsValid(viewer?.email)) {
// Note: we don't want to do a `hasProAccess` check to abort the purchase.
// Instead, we let them through so that they can make the purchase because
// if they already have Pro, then this upgrades them to Lifetime Pro.

// the user doesn't have pro access, proceed to checkout

const {sessionUrl, error} = await fetch('/api/stripe/checkout/lifetime', {
method: 'POST',
body: JSON.stringify({
email: viewer.email,
successPath: '/confirm/forever',
cancelPath: '/pricing/forever',
}),
}).then((res) => res.json())
if (sessionUrl) {
router.push(sessionUrl)
} else {
console.error('error creating checkout session', error)
}
} else {
// const couponCode = state.context.couponToApply?.couponCode

router.push(
'/forever/email?' +
new URLSearchParams({
priceId,
quantity: quantity.toString(),
}),
)
setLoaderOn(true)
}
}

// TODO: make the priceId and display price come from env vars
// as an MVP, it is good enough for now to manually make sure those
// values match up with what is in Stripe.
const lifetimePlan = {price: 500, price_discounted: 250}

return (
<div className="flex flex-col items-center">
<div className="relative p-2 bg-gray-100 rounded-md shadow-lg dark:bg-gray-800 dark:shadow-none">
<div className="relative z-10 flex flex-col items-center max-w-sm px-5 py-5 text-gray-900 bg-white rounded-sm dark:text-white dark:bg-gray-900 sm:px-8 sm:py-12">
<PlanTitle>Lifetime Membership</PlanTitle>
{/* {!isPPP && appliedCoupon?.coupon_expires_at && !pricesLoading && (
<Countdown
label="Save on Yearly Memberships Price goes up in:"
date={fromUnixTime(appliedCoupon.coupon_expires_at)}
/>
)} */}
<div className="py-6">
<PlanPrice
pricesLoading={pricesLoading}
plan={lifetimePlan}
amountPaid={amountPaid}
/>
</div>
{/* {!appliedCoupon && <PlanPercentageOff interval={currentPlan.name} />}
{quantityAvailable && (
<div className="my-4">
<PlanQuantitySelect
quantity={currentQuantity}
plan={currentPlan}
pricesLoading={pricesLoading}
onQuantityChanged={(quantity: number) => {
onQuantityChanged(quantity)
}}
/>
</div>
)} */}

<PlanFeatures planFeatures={DEFAULT_FEATURES} />
<GetAccessButton
label={
amountPaid ? 'Upgrade to Lifetime Access' : 'Get Lifetime Access'
}
handleClick={onClickCheckout}
loaderOn={loaderOn}
pricesLoading={pricesLoading}
/>
</div>
{/* <SelectPlanNew
prices={prices}
pricesLoading={pricesLoading}
handleClickGetAccess={() => {
send({type: 'CONFIRM_PRICE', onClickCheckout})
}}
quantityAvailable={true}
onQuantityChanged={(quantity: number) => {
send({type: 'CHANGE_QUANTITY', quantity})
}}
onPriceChanged={(priceId: string) => {
send({type: 'SWITCH_PRICE', priceId})
}}
currentPlan={currentPlan}
currentQuantity={quantity}
loaderOn={loaderOn}
appliedCoupon={appliedCoupon}
isPPP={pppCouponIsApplied}
/> */}
</div>
{/* {pppCouponAvailable && pppCouponEligible && (
<div className="max-w-screen-md pb-5 mx-auto mt-4">
<ParityCouponMessage
coupon={parityCoupon as Coupon}
countryName={countryName as string}
onApply={onApplyParityCoupon}
onDismiss={onDismissParityCoupon}
isPPP={pppCouponIsApplied}
/>
</div>
)} */}
<div className="flex sm:flex-row flex-col items-center py-24 sm:space-x-5 sm:space-y-0 space-y-5">
<PoweredByStripe />
<div className="text-sm">30 day money back guarantee</div>
</div>
</div>
)
}

// TODO: Extract PlanTitle and PlanPrice to shared components.

export const PlanPrice: React.FunctionComponent<
React.PropsWithChildren<{
Expand Down Expand Up @@ -151,155 +277,17 @@ const PlanFeatures: React.FunctionComponent<
)
}

const LifetimePricingWidget: FunctionComponent<
React.PropsWithChildren<{lastCharge: {amountPaid: number}}>
> = ({lastCharge}) => {
const {viewer, authToken} = useViewer()

const router = useRouter()
const params = useSearchParams()
const stripeParam = params?.get('stripe')

const [loaderOn, setLoaderOn] = React.useState<boolean>(false)

const priceId = process.env.NEXT_PUBLIC_STRIPE_LIFETIME_MEMBERSHIP_PRICE_ID
const quantity = 1
const pricesLoading = false

const amountPaid =
lastCharge?.amountPaid === 0 ? null : lastCharge?.amountPaid

const onClickCheckout = async () => {
if (!priceId) return
track('lifetime checkout: selected plan', {
priceId: priceId,
})

if (emailIsValid(viewer?.email)) {
// Note: we don't want to do a `hasProAccess` check to abort the purchase.
// Instead, we let them through so that they can make the purchase because
// if they already have Pro, then this upgrades them to Lifetime Pro.

// the user doesn't have pro access, proceed to checkout
track('lifetime checkout: valid email present', {
priceId: priceId,
})

const {sessionUrl, error} = await fetch('/api/stripe/checkout/lifetime', {
method: 'POST',
body: JSON.stringify({
email: viewer.email,
successPath: '/confirm/forever',
cancelPath: '/pricing/forever',
}),
}).then((res) => res.json())
if (sessionUrl) {
router.push(sessionUrl)
} else {
console.error('error creating checkout session', error)
}
} else {
track('checkout: get email', {
priceId: priceId,
})

// const couponCode = state.context.couponToApply?.couponCode

router.push(
'/forever/email?' +
new URLSearchParams({
priceId,
quantity: quantity.toString(),
}),
)
setLoaderOn(true)
}
}

// TODO: make the priceId and display price come from env vars
// as an MVP, it is good enough for now to manually make sure those
// values match up with what is in Stripe.
const lifetimePlan = {price: 500, price_discounted: 250}

return (
<div className="flex flex-col items-center">
<div className="relative p-2 bg-gray-100 rounded-md shadow-lg dark:bg-gray-800 dark:shadow-none">
<div className="relative z-10 flex flex-col items-center max-w-sm px-5 py-5 text-gray-900 bg-white rounded-sm dark:text-white dark:bg-gray-900 sm:px-8 sm:py-12">
<PlanTitle>Lifetime Membership</PlanTitle>
{/* {!isPPP && appliedCoupon?.coupon_expires_at && !pricesLoading && (
<Countdown
label="Save on Yearly Memberships Price goes up in:"
date={fromUnixTime(appliedCoupon.coupon_expires_at)}
/>
)} */}
<div className="py-6">
<PlanPrice
pricesLoading={pricesLoading}
plan={lifetimePlan}
amountPaid={amountPaid}
/>
</div>
{/* {!appliedCoupon && <PlanPercentageOff interval={currentPlan.name} />}
{quantityAvailable && (
<div className="my-4">
<PlanQuantitySelect
quantity={currentQuantity}
plan={currentPlan}
pricesLoading={pricesLoading}
onQuantityChanged={(quantity: number) => {
onQuantityChanged(quantity)
}}
/>
</div>
)} */}

<PlanFeatures planFeatures={DEFAULT_FEATURES} />
<GetAccessButton
label={
amountPaid ? 'Upgrade to Lifetime Access' : 'Get Lifetime Access'
}
handleClick={onClickCheckout}
loaderOn={loaderOn}
pricesLoading={pricesLoading}
/>
</div>
{/* <SelectPlanNew
prices={prices}
pricesLoading={pricesLoading}
handleClickGetAccess={() => {
send({type: 'CONFIRM_PRICE', onClickCheckout})
}}
quantityAvailable={true}
onQuantityChanged={(quantity: number) => {
send({type: 'CHANGE_QUANTITY', quantity})
}}
onPriceChanged={(priceId: string) => {
send({type: 'SWITCH_PRICE', priceId})
}}
currentPlan={currentPlan}
currentQuantity={quantity}
loaderOn={loaderOn}
appliedCoupon={appliedCoupon}
isPPP={pppCouponIsApplied}
/> */}
</div>
{/* {pppCouponAvailable && pppCouponEligible && (
<div className="max-w-screen-md pb-5 mx-auto mt-4">
<ParityCouponMessage
coupon={parityCoupon as Coupon}
countryName={countryName as string}
onApply={onApplyParityCoupon}
onDismiss={onDismissParityCoupon}
isPPP={pppCouponIsApplied}
/>
</div>
)} */}
<div className="flex sm:flex-row flex-col items-center py-24 sm:space-x-5 sm:space-y-0 space-y-5">
<PoweredByStripe />
<div className="text-sm">30 day money back guarantee</div>
</div>
</div>
)
}
export const PlanTitle: React.FunctionComponent<
React.PropsWithChildren<{className?: string}>
> = ({children, className}) => (
<h2
className={twMerge(
`text-xl font-bold text-gray-900 dark:text-white`,
className,
)}
>
{children}
</h2>
)

export default LifetimePricingWidget
export default ActiveSale
25 changes: 25 additions & 0 deletions src/components/pricing/lifetime/lifetime-pricing-widget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use client'
import * as React from 'react'
import {FunctionComponent} from 'react'
import {useSearchParams} from 'next/navigation'
import ActiveSale from './active-sale'
import UpcomingSale from './upcoming-sale'

const LifetimePricingWidget: FunctionComponent<
React.PropsWithChildren<{lastCharge: {amountPaid: number}}>
> = ({lastCharge}) => {
const searchParams = useSearchParams()
const allowPurchase = searchParams?.get('allowPurchase') ?? false

return (
<>
{allowPurchase === 'true' ? (
<ActiveSale lastCharge={lastCharge} />
) : (
<UpcomingSale />
)}
</>
)
}

export default LifetimePricingWidget
Loading
Loading