mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-09-03 06:12:53 +02:00
Update course landing page banner
This commit is contained in:
@@ -1,10 +1,7 @@
|
|||||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||||
import {
|
import {
|
||||||
ArrowRightIcon,
|
ArrowRightIcon, MousePointerClick,
|
||||||
CheckIcon,
|
Play
|
||||||
CopyIcon,
|
|
||||||
MousePointerClick,
|
|
||||||
Play,
|
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { cn } from '../../lib/classname';
|
import { cn } from '../../lib/classname';
|
||||||
@@ -24,7 +21,6 @@ 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 { sqlCouponCode } from './CourseDiscountBanner';
|
|
||||||
import { useCopyText } from '../../hooks/use-copy-text';
|
import { useCopyText } from '../../hooks/use-copy-text';
|
||||||
|
|
||||||
export const SQL_COURSE_SLUG = 'sql';
|
export const SQL_COURSE_SLUG = 'sql';
|
||||||
@@ -218,31 +214,6 @@ export function BuyButton(props: BuyButtonProps) {
|
|||||||
<CourseLoginPopup onClose={() => setIsLoginPopupOpen(false)} />
|
<CourseLoginPopup onClose={() => setIsLoginPopupOpen(false)} />
|
||||||
);
|
);
|
||||||
|
|
||||||
const mainCouponAlert = (
|
|
||||||
<div data-coupon-alert className="absolute top-1/2 -left-59 z-50 hidden -translate-y-1/2 md:block">
|
|
||||||
<div className="relative flex items-center rounded-xl bg-yellow-50 px-3 py-1.5 shadow-lg">
|
|
||||||
<div className="absolute top-1/2 -right-0.5 h-1.5 w-1.5 -translate-y-1/2 rotate-45 bg-yellow-50"></div>
|
|
||||||
<span className="text-xs font-bold text-black">
|
|
||||||
🎁 30% OFF with code{' '}
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
copyText(sqlCouponCode);
|
|
||||||
}}
|
|
||||||
className="inline-block rounded bg-black px-1.5 py-0.5 font-mono text-white hover:opacity-75"
|
|
||||||
>
|
|
||||||
{sqlCouponCode}
|
|
||||||
{isCopied && (
|
|
||||||
<CheckIcon className="relative -top-[2px] ml-1.5 inline-block size-3" />
|
|
||||||
)}
|
|
||||||
{!isCopied && (
|
|
||||||
<CopyIcon className="relative -top-px ml-1.5 inline-block size-3 text-gray-500" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (variant === 'main') {
|
if (variant === 'main') {
|
||||||
return (
|
return (
|
||||||
<div className="relative flex w-full flex-col items-center gap-2 md:w-auto">
|
<div className="relative flex w-full flex-col items-center gap-2 md:w-auto">
|
||||||
@@ -254,8 +225,6 @@ export function BuyButton(props: BuyButtonProps) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="relative flex flex-col gap-2 md:flex-row md:gap-0">
|
<div className="relative flex flex-col gap-2 md:flex-row md:gap-0">
|
||||||
{!isLoadingPricing && !isAlreadyEnrolled && mainCouponAlert}
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={onBuyClick}
|
onClick={onBuyClick}
|
||||||
disabled={isLoadingPricing}
|
disabled={isLoadingPricing}
|
||||||
|
@@ -1,79 +0,0 @@
|
|||||||
import { CheckIcon, CopyIcon, XIcon } from 'lucide-react';
|
|
||||||
import { useState, useEffect } from 'react';
|
|
||||||
import { useCopyText } from '../../hooks/use-copy-text';
|
|
||||||
import { cn } from '../../lib/classname';
|
|
||||||
import { SQL_COURSE_SLUG } from './BuyButton';
|
|
||||||
import { queryClient } from '../../stores/query-client';
|
|
||||||
import { courseProgressOptions } from '../../queries/course-progress';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { useClientMount } from '../../hooks/use-client-mount';
|
|
||||||
|
|
||||||
export const sqlCouponCode = 'SQL30';
|
|
||||||
|
|
||||||
export function CourseDiscountBanner() {
|
|
||||||
const { copyText, isCopied } = useCopyText();
|
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
|
||||||
const isClientMounted = useClientMount();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const timer = setTimeout(() => setIsVisible(true), 5000);
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const { data: courseProgress, isLoading: isLoadingCourseProgress } = useQuery(
|
|
||||||
courseProgressOptions(SQL_COURSE_SLUG),
|
|
||||||
queryClient,
|
|
||||||
);
|
|
||||||
|
|
||||||
const isAlreadyEnrolled = !!courseProgress?.enrolledAt;
|
|
||||||
if (!isClientMounted || isLoadingCourseProgress || isAlreadyEnrolled) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-coupon-alert
|
|
||||||
className={cn(
|
|
||||||
'sticky top-0 z-[999] flex w-full items-center justify-center overflow-hidden bg-yellow-500 text-center text-sm font-medium transition-[height] duration-300',
|
|
||||||
isVisible ? 'h-[34px] sm:h-[35px]' : 'h-0',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span className="mr-1 hidden font-bold sm:block">
|
|
||||||
🎁 Limited time offer :
|
|
||||||
</span>
|
|
||||||
Get 30% off using{' '}
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
copyText(sqlCouponCode);
|
|
||||||
}}
|
|
||||||
className={cn(
|
|
||||||
'animate-wiggle ml-1 inline-block cursor-pointer rounded-md border border-dashed border-black bg-gray-900 px-1.5 py-[3px] text-xs text-white [animation-delay:0.25s]',
|
|
||||||
isCopied && 'bg-gray-900 text-white',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span className="mr-1">Coupon code :</span>
|
|
||||||
{sqlCouponCode}
|
|
||||||
{isCopied && (
|
|
||||||
<CheckIcon
|
|
||||||
className="relative -top-[2px] ml-1.5 inline-block size-3"
|
|
||||||
strokeWidth={2.5}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!isCopied && (
|
|
||||||
<CopyIcon
|
|
||||||
className="relative -top-[2px] ml-1.5 inline-block size-3"
|
|
||||||
strokeWidth={2.5}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
setIsVisible(false);
|
|
||||||
}}
|
|
||||||
className="absolute top-1/2 right-4 hidden -translate-y-1/2 rounded-md px-1.5 py-1.5 hover:bg-yellow-600 hover:text-black sm:block"
|
|
||||||
>
|
|
||||||
<XIcon className="size-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@@ -28,7 +28,6 @@ import { PlatformDemo } from './PlatformDemo';
|
|||||||
import { ReviewsSection } from './ReviewsSection';
|
import { ReviewsSection } from './ReviewsSection';
|
||||||
import { SectionHeader } from './SectionHeader';
|
import { SectionHeader } from './SectionHeader';
|
||||||
import { Spotlight } from './Spotlight';
|
import { Spotlight } from './Spotlight';
|
||||||
import { CourseDiscountBanner } from './CourseDiscountBanner';
|
|
||||||
|
|
||||||
type ChapterData = {
|
type ChapterData = {
|
||||||
icon: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
@@ -245,7 +244,6 @@ export const sqlCourseChapters: ChapterData[] = [
|
|||||||
export function SQLCoursePage() {
|
export function SQLCoursePage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CourseDiscountBanner />
|
|
||||||
<div className="relative flex grow flex-col items-center bg-linear-to-b from-zinc-900 to-zinc-950 px-4 pt-3 pb-52 text-zinc-400 md:px-10 md:pt-8">
|
<div className="relative flex grow flex-col items-center bg-linear-to-b from-zinc-900 to-zinc-950 px-4 pt-3 pb-52 text-zinc-400 md:px-10 md:pt-8">
|
||||||
<div className="flex w-full items-center justify-between">
|
<div className="flex w-full items-center justify-between">
|
||||||
<a
|
<a
|
||||||
|
@@ -1,10 +1,7 @@
|
|||||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||||
import {
|
import {
|
||||||
ArrowRightIcon,
|
ArrowRightIcon, MousePointerClick,
|
||||||
CheckIcon,
|
Play
|
||||||
CopyIcon,
|
|
||||||
MousePointerClick,
|
|
||||||
Play,
|
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { cn } from '../../lib/classname';
|
import { cn } from '../../lib/classname';
|
||||||
@@ -25,7 +22,6 @@ 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 { useCopyText } from '../../hooks/use-copy-text';
|
import { useCopyText } from '../../hooks/use-copy-text';
|
||||||
import { sqlCouponCode } from './CourseDiscountBanner';
|
|
||||||
|
|
||||||
export const SQL_COURSE_SLUG = 'sql';
|
export const SQL_COURSE_SLUG = 'sql';
|
||||||
|
|
||||||
@@ -219,34 +215,6 @@ export function BuyButton(props: BuyButtonProps) {
|
|||||||
<CourseLoginPopup onClose={() => setIsLoginPopupOpen(false)} />
|
<CourseLoginPopup onClose={() => setIsLoginPopupOpen(false)} />
|
||||||
);
|
);
|
||||||
|
|
||||||
const mainCouponAlert = (
|
|
||||||
<div
|
|
||||||
data-coupon-alert
|
|
||||||
className="absolute top-1/2 -left-59 z-50 hidden -translate-y-1/2 md:block"
|
|
||||||
>
|
|
||||||
<div className="relative flex items-center rounded-xl bg-yellow-50 px-3 py-1.5 shadow-lg">
|
|
||||||
<div className="absolute top-1/2 -right-0.5 h-1.5 w-1.5 -translate-y-1/2 rotate-45 bg-yellow-50"></div>
|
|
||||||
<span className="text-xs font-bold text-black">
|
|
||||||
🎁 30% OFF with code{' '}
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
copyText(sqlCouponCode);
|
|
||||||
}}
|
|
||||||
className="inline-block rounded bg-black px-1.5 py-0.5 font-mono text-white hover:opacity-75"
|
|
||||||
>
|
|
||||||
{sqlCouponCode}
|
|
||||||
{isCopied && (
|
|
||||||
<CheckIcon className="relative -top-[2px] ml-1.5 inline-block size-3" />
|
|
||||||
)}
|
|
||||||
{!isCopied && (
|
|
||||||
<CopyIcon className="relative -top-px ml-1.5 inline-block size-3 text-gray-500" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (variant === 'main') {
|
if (variant === 'main') {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -260,8 +228,6 @@ export function BuyButton(props: BuyButtonProps) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="relative flex flex-col gap-2 md:flex-row md:gap-0">
|
<div className="relative flex flex-col gap-2 md:flex-row md:gap-0">
|
||||||
{!isLoadingPricing && !isAlreadyEnrolled && mainCouponAlert}
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={onBuyClick}
|
onClick={onBuyClick}
|
||||||
disabled={isLoadingPricing}
|
disabled={isLoadingPricing}
|
||||||
|
Reference in New Issue
Block a user