1
0
mirror of https://github.com/kamranahmedse/developer-roadmap.git synced 2025-08-29 20:21:50 +02:00

Add sql coupon code

This commit is contained in:
Kamran Ahmed
2025-06-05 19:12:21 +01:00
parent c8ba7578d2
commit fda439f0e9
4 changed files with 300 additions and 194 deletions

View File

@@ -1,5 +1,11 @@
import { useMutation, useQuery } from '@tanstack/react-query';
import { ArrowRightIcon, MousePointerClick, Play } from 'lucide-react';
import {
ArrowRightIcon,
CheckIcon,
CopyIcon,
MousePointerClick,
Play,
} from 'lucide-react';
import { useEffect, useState } from 'react';
import { cn } from '../../lib/classname';
import {
@@ -18,6 +24,8 @@ import { useToast } from '../../hooks/use-toast';
import { httpPost } from '../../lib/query-http';
import { deleteUrlParam, getUrlParams } from '../../lib/browser';
import { VideoModal } from '../VideoModal';
import { sqlCouponCode } from './CourseDiscountBanner';
import { useCopyText } from '../../hooks/use-copy-text';
export const SQL_COURSE_SLUG = 'sql';
@@ -43,6 +51,8 @@ export function BuyButton(props: BuyButtonProps) {
const [isVideoModalOpen, setIsVideoModalOpen] = useState(false);
const toast = useToast();
const { copyText, isCopied } = useCopyText();
const { data: coursePricing, isLoading: isLoadingPrice } = useQuery(
coursePriceOptions({ courseSlug: SQL_COURSE_SLUG }),
queryClient,
@@ -208,6 +218,31 @@ export function BuyButton(props: BuyButtonProps) {
<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') {
return (
<div className="relative flex w-full flex-col items-center gap-2 md:w-auto">
@@ -218,7 +253,9 @@ export function BuyButton(props: BuyButtonProps) {
onClose={() => setIsVideoModalOpen(false)}
/>
)}
<div className="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
onClick={onBuyClick}
disabled={isLoadingPricing}
@@ -272,7 +309,7 @@ export function BuyButton(props: BuyButtonProps) {
</div>
{!isLoadingPricing && (
<span className="absolute top-full z-50 flex w-[300px] translate-y-4 flex-row items-center justify-center text-sm text-yellow-400">
<span className="absolute top-full z-50 flex w-max translate-y-4 flex-row items-center justify-center text-sm text-yellow-400">
Lifetime access <span className="mx-2">&middot;</span>{' '}
<button
onClick={() => setIsVideoModalOpen(true)}

View File

@@ -0,0 +1,63 @@
import { CheckIcon, CopyIcon, XIcon } from 'lucide-react';
import { useState, useEffect } from 'react';
import { useCopyText } from '../../hooks/use-copy-text';
import { cn } from '../../lib/classname';
export const sqlCouponCode = 'SQL30';
export function CourseDiscountBanner() {
const { copyText, isCopied } = useCopyText();
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const timer = setTimeout(() => setIsVisible(true), 5000);
return () => clearTimeout(timer);
}, []);
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>
);
}

View File

@@ -28,6 +28,7 @@ import { PlatformDemo } from './PlatformDemo';
import { ReviewsSection } from './ReviewsSection';
import { SectionHeader } from './SectionHeader';
import { Spotlight } from './Spotlight';
import { CourseDiscountBanner } from './CourseDiscountBanner';
type ChapterData = {
icon: React.ReactNode;
@@ -245,7 +246,9 @@ export function SQLCoursePage() {
];
return (
<div className="relative flex grow flex-col items-center bg-linear-to-b from-zinc-900 to-zinc-950 px-4 pb-52 pt-3 text-zinc-400 md:px-10 md:pt-8">
<>
<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="flex w-full items-center justify-between">
<a
href="https://roadmap.sh"
@@ -257,7 +260,7 @@ export function SQLCoursePage() {
<AccountButton />
</div>
<div className="relative mt-7 max-w-4xl text-left md:mt-20 md:text-center">
<Spotlight className="left-[-170px] top-[-200px]" fill="#EAB308" />
<Spotlight className="top-[-200px] left-[-170px]" fill="#EAB308" />
<div className="inline-block rounded-full bg-yellow-500/10 px-4 py-1.5 text-base text-yellow-500 md:px-6 md:py-2 md:text-lg">
<span className="hidden sm:block">
@@ -267,7 +270,8 @@ export function SQLCoursePage() {
</div>
<h1 className="mt-5 text-4xl font-bold tracking-tight text-white md:mt-8 md:text-7xl">
Master SQL <span className="hidden min-[384px]:inline">Queries</span>
Master SQL{' '}
<span className="hidden min-[384px]:inline">Queries</span>
<div className="mt-2.5 bg-linear-to-r from-yellow-500 to-yellow-300 bg-clip-text text-transparent md:text-6xl lg:text-7xl">
From Basic to Advanced
</div>
@@ -387,9 +391,10 @@ export function SQLCoursePage() {
<div className="mt-2 flex flex-col gap-4 text-lg leading-[1.52] md:mt-4 md:gap-6 md:text-xl">
<p>
I am Kamran Ahmed, an engineering leader with over a decade of
experience in the tech industry. Throughout my career I have built
and scaled software systems, architected complex data systems, and
worked with large amounts of data to create efficient solutions.
experience in the tech industry. Throughout my career I have
built and scaled software systems, architected complex data
systems, and worked with large amounts of data to create
efficient solutions.
</p>
<p>
I am also the creator of{' '}
@@ -407,8 +412,8 @@ export function SQLCoursePage() {
<p>
In this course, I will share everything I have learned about SQL
from the basics to advanced concepts in a way that is easy to
understand and apply. Whether you are just starting or looking to
sharpen your skills, you are in the right place.
understand and apply. Whether you are just starting or looking
to sharpen your skills, you are in the right place.
</p>
</div>
}
@@ -421,7 +426,7 @@ export function SQLCoursePage() {
<FloatingPurchase />
<div className="mt-12 w-full max-w-3xl text-left md:mt-9">
<p className="flex flex-col gap-2 items-center justify-center text-sm md:flex-row md:gap-0">
<p className="flex flex-col items-center justify-center gap-2 text-sm md:flex-row md:gap-0">
<a href="/terms" target="_blank" className="text-zinc-500">
Terms of Use
</a>
@@ -432,5 +437,6 @@ export function SQLCoursePage() {
</p>
</div>
</div>
</>
);
}

View File

@@ -38,13 +38,13 @@ module.exports = {
},
},
spotlight: {
"0%": {
'0%': {
opacity: 0,
transform: "translate(-72%, -62%) scale(0.5)",
transform: 'translate(-72%, -62%) scale(0.5)',
},
"100%": {
'100%': {
opacity: 1,
transform: "translate(-50%,-40%) scale(1)",
transform: 'translate(-50%,-40%) scale(1)',
},
},
wiggle: {
@@ -60,7 +60,7 @@ module.exports = {
'fade-slide-up':
'fade-slide-up 0.2s cubic-bezier(0.4, 0, 0.2, 1) forwards',
'fade-in': 'fade-in 0.2s cubic-bezier(0.4, 0, 0.2, 1) forwards',
spotlight: "spotlight 2s ease 0.25s 1 forwards",
spotlight: 'spotlight 2s ease 0.25s 1 forwards',
wiggle: 'wiggle 0.5s ease-in-out forwards',
},
},