Add course demo button

feat/course-demo
Kamran Ahmed 7 days ago
parent e5e43de98a
commit c40b378202
  1. 6
      src/components/AuthenticationFlow/CourseLoginPopup.tsx
  2. 103
      src/components/SQLCourse/BuyButton.tsx

@ -12,6 +12,7 @@ type CourseLoginPopupProps = {
}; };
export const CHECKOUT_AFTER_LOGIN_KEY = 'checkoutAfterLogin'; export const CHECKOUT_AFTER_LOGIN_KEY = 'checkoutAfterLogin';
export const SAMPLE_AFTER_LOGIN_KEY = 'sampleAfterLogin';
export function CourseLoginPopup(props: CourseLoginPopupProps) { export function CourseLoginPopup(props: CourseLoginPopupProps) {
const { onClose: parentOnClose, checkoutAfterLogin = true } = props; const { onClose: parentOnClose, checkoutAfterLogin = true } = props;
@ -27,6 +28,7 @@ export function CourseLoginPopup(props: CourseLoginPopupProps) {
// if user didn't login and closed the popup, we remove the checkoutAfterLogin flag // if user didn't login and closed the popup, we remove the checkoutAfterLogin flag
// so that login from other buttons on course page will trigger purchase // so that login from other buttons on course page will trigger purchase
localStorage.removeItem(CHECKOUT_AFTER_LOGIN_KEY); localStorage.removeItem(CHECKOUT_AFTER_LOGIN_KEY);
localStorage.removeItem(SAMPLE_AFTER_LOGIN_KEY);
parentOnClose(); parentOnClose();
} }
@ -40,7 +42,7 @@ export function CourseLoginPopup(props: CourseLoginPopupProps) {
if (emailNature) { if (emailNature) {
const emailHeader = ( const emailHeader = (
<div className="mb-7 text-center"> <div className="mb-7 text-center">
<p className="mb-3.5 pt-2 text-2xl font-semibold leading-5 text-slate-900"> <p className="mb-3.5 pt-2 text-2xl leading-5 font-semibold text-slate-900">
{emailNature === 'login' {emailNature === 'login'
? 'Login to your account' ? 'Login to your account'
: 'Create an account'} : 'Create an account'}
@ -80,7 +82,7 @@ export function CourseLoginPopup(props: CourseLoginPopupProps) {
return ( return (
<Modal onClose={onClose} bodyClassName="p-5 h-auto"> <Modal onClose={onClose} bodyClassName="p-5 h-auto">
<div className="mb-7 text-center"> <div className="mb-7 text-center">
<p className="mb-3.5 pt-2 text-2xl font-semibold leading-5 text-slate-900"> <p className="mb-3.5 pt-2 text-2xl leading-5 font-semibold text-slate-900">
Create or login to Enroll Create or login to Enroll
</p> </p>
<p className="mt-2 text-sm leading-4 text-slate-600"> <p className="mt-2 text-sm leading-4 text-slate-600">

@ -1,5 +1,5 @@
import { useMutation, useQuery } from '@tanstack/react-query'; import { useMutation, useQuery } from '@tanstack/react-query';
import { ArrowRightIcon, Play } from 'lucide-react'; import { ArrowRightIcon, BookOpen, Play } from 'lucide-react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { cn } from '../../lib/classname'; import { cn } from '../../lib/classname';
import { import {
@ -10,12 +10,14 @@ import {
import { coursePriceOptions } from '../../queries/billing'; import { coursePriceOptions } from '../../queries/billing';
import { courseProgressOptions } from '../../queries/course-progress'; import { courseProgressOptions } from '../../queries/course-progress';
import { queryClient } from '../../stores/query-client'; import { queryClient } from '../../stores/query-client';
import { CourseLoginPopup } from '../AuthenticationFlow/CourseLoginPopup'; import {
CourseLoginPopup,
SAMPLE_AFTER_LOGIN_KEY,
} from '../AuthenticationFlow/CourseLoginPopup';
import { useToast } from '../../hooks/use-toast'; import { useToast } from '../../hooks/use-toast';
import { httpPost } from '../../lib/query-http'; import { httpPost } from '../../lib/query-http';
import { deleteUrlParam, getUrlParams } from '../../lib/browser'; import { deleteUrlParam, getUrlParams } from '../../lib/browser';
import { VideoModal } from '../VideoModal'; import { VideoModal } from '../VideoModal';
import { showLoginPopup } from '../../lib/popup';
export const SQL_COURSE_SLUG = 'sql'; export const SQL_COURSE_SLUG = 'sql';
@ -96,7 +98,13 @@ export function BuyButton(props: BuyButtonProps) {
useEffect(() => { useEffect(() => {
const urlParams = getUrlParams(); const urlParams = getUrlParams();
const shouldTriggerPurchase = urlParams[COURSE_PURCHASE_PARAM] === '1'; const shouldTriggerPurchase = urlParams[COURSE_PURCHASE_PARAM] === '1';
if (shouldTriggerPurchase) { const shouldTriggerSample =
localStorage.getItem(SAMPLE_AFTER_LOGIN_KEY) === '1';
if (shouldTriggerSample) {
localStorage.removeItem(SAMPLE_AFTER_LOGIN_KEY);
window.location.href = `${import.meta.env.PUBLIC_COURSE_APP_URL}/${SQL_COURSE_SLUG}`;
} else if (shouldTriggerPurchase) {
deleteUrlParam(COURSE_PURCHASE_PARAM); deleteUrlParam(COURSE_PURCHASE_PARAM);
initPurchase(); initPurchase();
} }
@ -163,6 +171,16 @@ export function BuyButton(props: BuyButtonProps) {
initPurchase(); initPurchase();
} }
function onReadSampleClick() {
if (!isLoggedIn()) {
localStorage.setItem(SAMPLE_AFTER_LOGIN_KEY, '1');
setIsLoginPopupOpen(true);
return;
}
window.location.href = `${import.meta.env.PUBLIC_COURSE_APP_URL}/${SQL_COURSE_SLUG}`;
}
const courseLoginPopup = isLoginPopupOpen && ( const courseLoginPopup = isLoginPopupOpen && (
<CourseLoginPopup onClose={() => setIsLoginPopupOpen(false)} /> <CourseLoginPopup onClose={() => setIsLoginPopupOpen(false)} />
); );
@ -177,43 +195,54 @@ export function BuyButton(props: BuyButtonProps) {
onClose={() => setIsVideoModalOpen(false)} onClose={() => setIsVideoModalOpen(false)}
/> />
)} )}
<button <div className="flex flex-row">
onClick={onBuyClick} <button
disabled={isLoadingPricing} onClick={onBuyClick}
className={cn( disabled={isLoadingPricing}
'group relative inline-flex w-full min-w-[235px] items-center justify-center overflow-hidden rounded-xl bg-linear-to-r from-yellow-500 to-yellow-300 px-8 py-3 text-base font-semibold text-black transition-all duration-300 ease-out hover:scale-[1.02] hover:shadow-[0_0_30px_rgba(234,179,8,0.4)] focus:outline-hidden active:ring-0 md:w-auto md:rounded-full md:text-lg', className={cn(
(isLoadingPricing || isCreatingCheckoutSession) && 'group relative mr-2 inline-flex w-full min-w-[235px] items-center justify-center overflow-hidden rounded-xl bg-linear-to-r from-yellow-500 to-yellow-300 px-8 py-3 text-base font-semibold text-black transition-all duration-300 ease-out hover:scale-[1.02] hover:shadow-[0_0_30px_rgba(234,179,8,0.4)] focus:outline-hidden active:ring-0 md:w-auto md:rounded-full md:text-lg',
'striped-loader-yellow pointer-events-none scale-105 bg-yellow-500', (isLoadingPricing || isCreatingCheckoutSession) &&
)} 'striped-loader-yellow pointer-events-none mr-4 scale-105 bg-yellow-500',
> )}
{isLoadingPricing ? ( >
<span className="relative flex items-center gap-2">&nbsp;</span> {isLoadingPricing ? (
) : isAlreadyEnrolled ? ( <span className="relative flex items-center gap-2">&nbsp;</span>
<span className="relative flex items-center gap-2"> ) : isAlreadyEnrolled ? (
Start Learning <span className="relative flex items-center gap-2">
</span> Start Learning
) : ( </span>
<span className="relative flex items-center gap-2"> ) : (
Buy now for{' '} <span className="relative flex items-center gap-2">
{coursePricing?.isEligibleForDiscount ? ( Buy now for{' '}
<span className="flex items-center gap-2"> {coursePricing?.isEligibleForDiscount ? (
<span className="hidden text-base line-through opacity-75 md:inline"> <span className="flex items-center gap-2">
${coursePricing?.fullPrice} <span className="hidden text-base line-through opacity-75 md:inline">
</span> ${coursePricing?.fullPrice}
<span className="text-base md:text-xl"> </span>
${coursePricing?.regionalPrice} <span className="text-base md:text-xl">
${coursePricing?.regionalPrice}
</span>
</span> </span>
</span> ) : (
) : ( <span>${coursePricing?.regionalPrice}</span>
<span>${coursePricing?.regionalPrice}</span> )}
)} <ArrowRightIcon className="h-5 w-5 transition-transform duration-300 ease-out group-hover:translate-x-1" />
<ArrowRightIcon className="h-5 w-5 transition-transform duration-300 ease-out group-hover:translate-x-1" /> </span>
)}
</button>
<button
onClick={onReadSampleClick}
className="group relative inline-flex items-center justify-center overflow-hidden rounded-xl border border-yellow-500/30 bg-transparent px-6 py-3 text-base font-medium text-yellow-500 transition-all duration-300 ease-out hover:bg-yellow-500/10 focus:outline-hidden active:ring-0 md:rounded-full"
>
<span className="relative flex items-center gap-2">
<BookOpen className="h-5 w-5" />
Read Sample
</span> </span>
)} </button>
</button> </div>
{!isLoadingPricing && ( {!isLoadingPricing && (
<span className="absolute top-full z-50 flex w-[300px] translate-y-3 flex-row items-center justify-center text-sm text-yellow-400"> <span className="absolute top-full z-50 flex w-[300px] translate-y-4 flex-row items-center justify-center text-sm text-yellow-400">
Lifetime access <span className="mx-2">&middot;</span>{' '} Lifetime access <span className="mx-2">&middot;</span>{' '}
<button <button
onClick={() => setIsVideoModalOpen(true)} onClick={() => setIsVideoModalOpen(true)}

Loading…
Cancel
Save