diff --git a/src/components/SQLCourse/SQLCoursePage.tsx b/src/components/SQLCourse/SQLCoursePage.tsx
index e1257c7b1..a7dc2bd36 100644
--- a/src/components/SQLCourse/SQLCoursePage.tsx
+++ b/src/components/SQLCourse/SQLCoursePage.tsx
@@ -39,212 +39,210 @@ type ChapterData = {
lessons: { title: string; type: 'lesson' | 'challenge' | 'quiz' }[];
};
-export function SQLCoursePage() {
- const chapters: ChapterData[] = [
- {
- icon: ,
- title: 'Introduction',
- description:
- 'Get comfortable with database concepts and SQL fundamentals.',
- lessonCount: 4,
- challengeCount: 1,
- lessons: [
- { title: 'Basics of Databases', type: 'lesson' },
- { title: 'What is SQL?', type: 'lesson' },
- { title: 'Types of Queries', type: 'lesson' },
- { title: 'Next Steps', type: 'lesson' },
- { title: 'Introduction Quiz', type: 'challenge' },
- ],
- },
- {
- icon: ,
- title: 'SQL Basics',
- description: 'Master the essential SQL query operations and syntax.',
- lessonCount: 9,
- challengeCount: 7,
- lessons: [
- { title: 'SELECT Fundamentals', type: 'lesson' },
- { title: 'Aliases and Constants', type: 'lesson' },
- { title: 'Expressions in SELECT', type: 'lesson' },
- { title: 'Selecting DISTINCT Values', type: 'lesson' },
- { title: 'Filtering with WHERE', type: 'lesson' },
- { title: 'Sorting with ORDER BY', type: 'lesson' },
- { title: 'Limiting Results with LIMIT', type: 'lesson' },
- { title: 'Handling NULL Values', type: 'lesson' },
- { title: 'Comments', type: 'lesson' },
- { title: 'Basic Queries Quiz', type: 'quiz' },
- { title: 'Projection Challenge', type: 'challenge' },
- { title: 'Select Expression', type: 'challenge' },
- { title: 'Select Unique', type: 'challenge' },
- { title: 'Logical Operators', type: 'challenge' },
- { title: 'Sorting Challenge', type: 'challenge' },
- { title: 'Sorting and Limiting', type: 'challenge' },
- { title: 'Sorting and Filtering', type: 'challenge' },
- ],
- },
- {
- icon: ,
- title: 'Manipulating Data',
- description: 'Learn how to modify and manipulate data in your database.',
- lessonCount: 3,
- challengeCount: 3,
- lessons: [
- { title: 'INSERT Operations', type: 'lesson' },
- { title: 'UPDATE Operations', type: 'lesson' },
- { title: 'DELETE Operations', type: 'lesson' },
- { title: 'Data Manipulation Quiz', type: 'quiz' },
- { title: 'Inserting Customers', type: 'challenge' },
- { title: 'Updating Bookstore', type: 'challenge' },
- { title: 'Deleting Books', type: 'challenge' },
- ],
- },
- {
- icon: ,
- title: 'Defining Tables',
- description: 'Master database schema design and table management.',
- lessonCount: 9,
- challengeCount: 7,
- lessons: [
- { title: 'Creating Tables', type: 'lesson' },
- { title: 'Data Types in SQLite', type: 'lesson' },
- { title: 'Common Data Types', type: 'lesson' },
- { title: 'More on Numeric Types', type: 'lesson' },
- { title: 'Temporal Data Types', type: 'lesson' },
- { title: 'CHECK Constraints', type: 'lesson' },
- { title: 'Primary Key Constraint', type: 'lesson' },
- { title: 'Modifying Tables', type: 'lesson' },
- { title: 'Dropping and Truncating', type: 'lesson' },
- { title: 'Defining Tables Quiz', type: 'quiz' },
- { title: 'Simple Table Creation', type: 'challenge' },
- { title: 'Data Types Challenge', type: 'challenge' },
- { title: 'Constraints Challenge', type: 'challenge' },
- { title: 'Temporal Validation', type: 'challenge' },
- { title: 'Sales Data Analysis', type: 'challenge' },
- { title: 'Modifying Tables', type: 'challenge' },
- { title: 'Removing Table Data', type: 'challenge' },
- ],
- },
- {
- icon: ,
- title: 'Multi-Table Queries',
- description:
- 'Learn to work with multiple tables using JOINs and relationships.',
- lessonCount: 7,
- challengeCount: 10,
- lessons: [
- { title: 'More on Relational Data', type: 'lesson' },
- { title: 'Relationships and Types', type: 'lesson' },
- { title: 'JOINs in Queries', type: 'lesson' },
- { title: 'Self Joins and Usecases', type: 'lesson' },
- { title: 'Foreign Key Constraint', type: 'lesson' },
- { title: 'Set Operator Queries', type: 'lesson' },
- { title: 'Views and Virtual Tables', type: 'lesson' },
- { title: 'Multi-Table Queries Quiz', type: 'quiz' },
- { title: 'Inactive Customers', type: 'challenge' },
- { title: 'Recent 3 Orders', type: 'challenge' },
- { title: 'High Value Orders', type: 'challenge' },
- { title: 'Specific Book Customers', type: 'challenge' },
- { title: 'Referred Customers', type: 'challenge' },
- { title: 'Readers Like You', type: 'challenge' },
- { title: 'Same Price Books', type: 'challenge' },
- { title: 'Multi-Section Authors', type: 'challenge' },
- { title: 'Expensive Books', type: 'challenge' },
- { title: 'Trending Tech Books', type: 'challenge' },
- ],
- },
- {
- icon: ,
- title: 'Aggregate Functions',
- description:
- "Analyze and summarize data using SQL's powerful aggregation features.",
- lessonCount: 4,
- challengeCount: 10,
- lessons: [
- { title: 'What is Aggregation?', type: 'lesson' },
- { title: 'Basic Aggregation', type: 'lesson' },
- { title: 'Grouping Data', type: 'lesson' },
- { title: 'Grouping and Filtering', type: 'lesson' },
- { title: 'Aggregate Queries Quiz', type: 'quiz' },
- { title: 'Book Sales Summary', type: 'challenge' },
- { title: 'Category Insights', type: 'challenge' },
- { title: 'Author Tier Analysis', type: 'challenge' },
- { title: 'Author Book Stats', type: 'challenge' },
- { title: 'Daily Sales Report', type: 'challenge' },
- { title: 'Publisher Stats', type: 'challenge' },
- { title: 'High Value Publishers', type: 'challenge' },
- { title: 'Premium Authors', type: 'challenge' },
- { title: 'Sales Analysis', type: 'challenge' },
- { title: 'Employee Performance', type: 'challenge' },
- ],
- },
- {
- icon: ,
- title: 'Scalar Functions',
- description:
- 'Master built-in functions for data transformation and manipulation.',
- lessonCount: 6,
- challengeCount: 5,
- lessons: [
- { title: 'What are they?', type: 'lesson' },
- { title: 'String Functions', type: 'lesson' },
- { title: 'Numeric Functions', type: 'lesson' },
- { title: 'Date Functions', type: 'lesson' },
- { title: 'Conversion Functions', type: 'lesson' },
- { title: 'Logical Functions', type: 'lesson' },
- { title: 'Scalar Functions Quiz', type: 'quiz' },
- { title: 'Customer Contact List', type: 'challenge' },
- { title: 'Membership Duration', type: 'challenge' },
- { title: 'Book Performance', type: 'challenge' },
- { title: 'Book Categories', type: 'challenge' },
- { title: 'Monthly Sales Analysis', type: 'challenge' },
- ],
- },
- {
- icon: ,
- title: 'Subqueries and CTEs',
- description:
- 'Write complex queries using subqueries and common table expressions.',
- lessonCount: 4,
- challengeCount: 6,
- lessons: [
- { title: 'What are Subqueries?', type: 'lesson' },
- { title: 'Correlated Subqueries', type: 'lesson' },
- { title: 'Common Table Expressions', type: 'lesson' },
- { title: 'Recursive CTEs', type: 'lesson' },
- { title: 'Subqueries Quiz', type: 'quiz' },
- { title: 'Books Above Average', type: 'challenge' },
- { title: 'Latest Category Books', type: 'challenge' },
- { title: 'Low Stock by Category', type: 'challenge' },
- { title: 'Bestseller Rankings', type: 'challenge' },
- { title: 'New Customer Analysis', type: 'challenge' },
- { title: 'Daily Sales Report', type: 'challenge' },
- ],
- },
- {
- icon: ,
- title: 'Window Functions',
- description:
- 'Advanced analytics and calculations using window functions.',
- lessonCount: 5,
- challengeCount: 7,
- lessons: [
- { title: 'What are they?', type: 'lesson' },
- { title: 'OVER and PARTITION BY', type: 'lesson' },
- { title: 'Use of ORDER BY', type: 'lesson' },
- { title: 'Ranking Functions', type: 'lesson' },
- { title: 'Window Frames', type: 'lesson' },
- { title: 'Window Functions Quiz', type: 'quiz' },
- { title: 'Basic Sales Metrics', type: 'challenge' },
- { title: 'Bestseller Comparison', type: 'challenge' },
- { title: 'Author Category Sales', type: 'challenge' },
- { title: 'Top Authors', type: 'challenge' },
- { title: 'Price Tier Rankings', type: 'challenge' },
- { title: 'Month-over-Month Sales', type: 'challenge' },
- { title: 'Price Range Analysis', type: 'challenge' },
- ],
- },
- ];
+export const sqlCourseChapters: ChapterData[] = [
+ {
+ icon: ,
+ title: 'Introduction',
+ description: 'Get comfortable with database concepts and SQL fundamentals.',
+ lessonCount: 4,
+ challengeCount: 1,
+ lessons: [
+ { title: 'Basics of Databases', type: 'lesson' },
+ { title: 'What is SQL?', type: 'lesson' },
+ { title: 'Types of Queries', type: 'lesson' },
+ { title: 'Next Steps', type: 'lesson' },
+ { title: 'Introduction Quiz', type: 'challenge' },
+ ],
+ },
+ {
+ icon: ,
+ title: 'SQL Basics',
+ description: 'Master the essential SQL query operations and syntax.',
+ lessonCount: 9,
+ challengeCount: 7,
+ lessons: [
+ { title: 'SELECT Fundamentals', type: 'lesson' },
+ { title: 'Aliases and Constants', type: 'lesson' },
+ { title: 'Expressions in SELECT', type: 'lesson' },
+ { title: 'Selecting DISTINCT Values', type: 'lesson' },
+ { title: 'Filtering with WHERE', type: 'lesson' },
+ { title: 'Sorting with ORDER BY', type: 'lesson' },
+ { title: 'Limiting Results with LIMIT', type: 'lesson' },
+ { title: 'Handling NULL Values', type: 'lesson' },
+ { title: 'Comments', type: 'lesson' },
+ { title: 'Basic Queries Quiz', type: 'quiz' },
+ { title: 'Projection Challenge', type: 'challenge' },
+ { title: 'Select Expression', type: 'challenge' },
+ { title: 'Select Unique', type: 'challenge' },
+ { title: 'Logical Operators', type: 'challenge' },
+ { title: 'Sorting Challenge', type: 'challenge' },
+ { title: 'Sorting and Limiting', type: 'challenge' },
+ { title: 'Sorting and Filtering', type: 'challenge' },
+ ],
+ },
+ {
+ icon: ,
+ title: 'Manipulating Data',
+ description: 'Learn how to modify and manipulate data in your database.',
+ lessonCount: 3,
+ challengeCount: 3,
+ lessons: [
+ { title: 'INSERT Operations', type: 'lesson' },
+ { title: 'UPDATE Operations', type: 'lesson' },
+ { title: 'DELETE Operations', type: 'lesson' },
+ { title: 'Data Manipulation Quiz', type: 'quiz' },
+ { title: 'Inserting Customers', type: 'challenge' },
+ { title: 'Updating Bookstore', type: 'challenge' },
+ { title: 'Deleting Books', type: 'challenge' },
+ ],
+ },
+ {
+ icon: ,
+ title: 'Defining Tables',
+ description: 'Master database schema design and table management.',
+ lessonCount: 9,
+ challengeCount: 7,
+ lessons: [
+ { title: 'Creating Tables', type: 'lesson' },
+ { title: 'Data Types in SQLite', type: 'lesson' },
+ { title: 'Common Data Types', type: 'lesson' },
+ { title: 'More on Numeric Types', type: 'lesson' },
+ { title: 'Temporal Data Types', type: 'lesson' },
+ { title: 'CHECK Constraints', type: 'lesson' },
+ { title: 'Primary Key Constraint', type: 'lesson' },
+ { title: 'Modifying Tables', type: 'lesson' },
+ { title: 'Dropping and Truncating', type: 'lesson' },
+ { title: 'Defining Tables Quiz', type: 'quiz' },
+ { title: 'Simple Table Creation', type: 'challenge' },
+ { title: 'Data Types Challenge', type: 'challenge' },
+ { title: 'Constraints Challenge', type: 'challenge' },
+ { title: 'Temporal Validation', type: 'challenge' },
+ { title: 'Sales Data Analysis', type: 'challenge' },
+ { title: 'Modifying Tables', type: 'challenge' },
+ { title: 'Removing Table Data', type: 'challenge' },
+ ],
+ },
+ {
+ icon: ,
+ title: 'Multi-Table Queries',
+ description:
+ 'Learn to work with multiple tables using JOINs and relationships.',
+ lessonCount: 7,
+ challengeCount: 10,
+ lessons: [
+ { title: 'More on Relational Data', type: 'lesson' },
+ { title: 'Relationships and Types', type: 'lesson' },
+ { title: 'JOINs in Queries', type: 'lesson' },
+ { title: 'Self Joins and Usecases', type: 'lesson' },
+ { title: 'Foreign Key Constraint', type: 'lesson' },
+ { title: 'Set Operator Queries', type: 'lesson' },
+ { title: 'Views and Virtual Tables', type: 'lesson' },
+ { title: 'Multi-Table Queries Quiz', type: 'quiz' },
+ { title: 'Inactive Customers', type: 'challenge' },
+ { title: 'Recent 3 Orders', type: 'challenge' },
+ { title: 'High Value Orders', type: 'challenge' },
+ { title: 'Specific Book Customers', type: 'challenge' },
+ { title: 'Referred Customers', type: 'challenge' },
+ { title: 'Readers Like You', type: 'challenge' },
+ { title: 'Same Price Books', type: 'challenge' },
+ { title: 'Multi-Section Authors', type: 'challenge' },
+ { title: 'Expensive Books', type: 'challenge' },
+ { title: 'Trending Tech Books', type: 'challenge' },
+ ],
+ },
+ {
+ icon: ,
+ title: 'Aggregate Functions',
+ description:
+ "Analyze and summarize data using SQL's powerful aggregation features.",
+ lessonCount: 4,
+ challengeCount: 10,
+ lessons: [
+ { title: 'What is Aggregation?', type: 'lesson' },
+ { title: 'Basic Aggregation', type: 'lesson' },
+ { title: 'Grouping Data', type: 'lesson' },
+ { title: 'Grouping and Filtering', type: 'lesson' },
+ { title: 'Aggregate Queries Quiz', type: 'quiz' },
+ { title: 'Book Sales Summary', type: 'challenge' },
+ { title: 'Category Insights', type: 'challenge' },
+ { title: 'Author Tier Analysis', type: 'challenge' },
+ { title: 'Author Book Stats', type: 'challenge' },
+ { title: 'Daily Sales Report', type: 'challenge' },
+ { title: 'Publisher Stats', type: 'challenge' },
+ { title: 'High Value Publishers', type: 'challenge' },
+ { title: 'Premium Authors', type: 'challenge' },
+ { title: 'Sales Analysis', type: 'challenge' },
+ { title: 'Employee Performance', type: 'challenge' },
+ ],
+ },
+ {
+ icon: ,
+ title: 'Scalar Functions',
+ description:
+ 'Master built-in functions for data transformation and manipulation.',
+ lessonCount: 6,
+ challengeCount: 5,
+ lessons: [
+ { title: 'What are they?', type: 'lesson' },
+ { title: 'String Functions', type: 'lesson' },
+ { title: 'Numeric Functions', type: 'lesson' },
+ { title: 'Date Functions', type: 'lesson' },
+ { title: 'Conversion Functions', type: 'lesson' },
+ { title: 'Logical Functions', type: 'lesson' },
+ { title: 'Scalar Functions Quiz', type: 'quiz' },
+ { title: 'Customer Contact List', type: 'challenge' },
+ { title: 'Membership Duration', type: 'challenge' },
+ { title: 'Book Performance', type: 'challenge' },
+ { title: 'Book Categories', type: 'challenge' },
+ { title: 'Monthly Sales Analysis', type: 'challenge' },
+ ],
+ },
+ {
+ icon: ,
+ title: 'Subqueries and CTEs',
+ description:
+ 'Write complex queries using subqueries and common table expressions.',
+ lessonCount: 4,
+ challengeCount: 6,
+ lessons: [
+ { title: 'What are Subqueries?', type: 'lesson' },
+ { title: 'Correlated Subqueries', type: 'lesson' },
+ { title: 'Common Table Expressions', type: 'lesson' },
+ { title: 'Recursive CTEs', type: 'lesson' },
+ { title: 'Subqueries Quiz', type: 'quiz' },
+ { title: 'Books Above Average', type: 'challenge' },
+ { title: 'Latest Category Books', type: 'challenge' },
+ { title: 'Low Stock by Category', type: 'challenge' },
+ { title: 'Bestseller Rankings', type: 'challenge' },
+ { title: 'New Customer Analysis', type: 'challenge' },
+ { title: 'Daily Sales Report', type: 'challenge' },
+ ],
+ },
+ {
+ icon: ,
+ title: 'Window Functions',
+ description: 'Advanced analytics and calculations using window functions.',
+ lessonCount: 5,
+ challengeCount: 7,
+ lessons: [
+ { title: 'What are they?', type: 'lesson' },
+ { title: 'OVER and PARTITION BY', type: 'lesson' },
+ { title: 'Use of ORDER BY', type: 'lesson' },
+ { title: 'Ranking Functions', type: 'lesson' },
+ { title: 'Window Frames', type: 'lesson' },
+ { title: 'Window Functions Quiz', type: 'quiz' },
+ { title: 'Basic Sales Metrics', type: 'challenge' },
+ { title: 'Bestseller Comparison', type: 'challenge' },
+ { title: 'Author Category Sales', type: 'challenge' },
+ { title: 'Top Authors', type: 'challenge' },
+ { title: 'Price Tier Rankings', type: 'challenge' },
+ { title: 'Month-over-Month Sales', type: 'challenge' },
+ { title: 'Price Range Analysis', type: 'challenge' },
+ ],
+ },
+];
+export function SQLCoursePage() {
return (
<>
@@ -379,7 +377,7 @@ export function SQLCoursePage() {
/>
- {chapters.map((chapter, index) => (
+ {sqlCourseChapters.map((chapter, index) => (
))}
diff --git a/src/components/SQLCourseVariant/AuthorCredentials.tsx b/src/components/SQLCourseVariant/AuthorCredentials.tsx
new file mode 100644
index 000000000..4c031e5d2
--- /dev/null
+++ b/src/components/SQLCourseVariant/AuthorCredentials.tsx
@@ -0,0 +1,68 @@
+import { AwardIcon, InfoIcon } from 'lucide-react';
+import { GitHubIcon } from '../ReactIcons/GitHubIcon';
+import { Popover, PopoverContent, PopoverTrigger } from '../Popover';
+
+export function AuthorCredentials() {
+ return (
+
+
+
+
+
+
+ by Kamran Ahmed
+
+
+ Your teacher for this course
+
+
+
+
+
+
+
+ #2 Most Starred Developer
+
+
+
+
+
+
+
+
+ Founder roadmap.sh
+
+
+
+
+
+
+ roadmap.sh
+ {' '}
+ provides community-curated roadmaps, study plans, paths, and
+ resources for developers and IT professionals. Serving 2M+
+ registered users, it is the 6th most-starred open source project
+ on GitHub
+
+
+
+
+
+ );
+}
diff --git a/src/components/SQLCourseVariant/BuyButton.tsx b/src/components/SQLCourseVariant/BuyButton.tsx
new file mode 100644
index 000000000..1c22a046e
--- /dev/null
+++ b/src/components/SQLCourseVariant/BuyButton.tsx
@@ -0,0 +1,381 @@
+import { useMutation, useQuery } from '@tanstack/react-query';
+import {
+ ArrowRightIcon,
+ CheckIcon,
+ CopyIcon,
+ MousePointerClick,
+ Play,
+} from 'lucide-react';
+import { useEffect, useState } from 'react';
+import { cn } from '../../lib/classname';
+import {
+ COURSE_PURCHASE_PARAM,
+ COURSE_PURCHASE_SUCCESS_PARAM,
+ isLoggedIn,
+} from '../../lib/jwt';
+import { coursePriceOptions } from '../../queries/billing';
+import { courseProgressOptions } from '../../queries/course-progress';
+import { queryClient } from '../../stores/query-client';
+import {
+ CourseLoginPopup,
+ SAMPLE_AFTER_LOGIN_KEY,
+} from '../AuthenticationFlow/CourseLoginPopup';
+import { useToast } from '../../hooks/use-toast';
+import { httpPost } from '../../lib/query-http';
+import { deleteUrlParam, getUrlParams } from '../../lib/browser';
+import { VideoModal } from '../VideoModal';
+import { useCopyText } from '../../hooks/use-copy-text';
+import { sqlCouponCode } from './CourseDiscountBanner';
+
+export const SQL_COURSE_SLUG = 'sql';
+
+type CreateCheckoutSessionBody = {
+ courseId: string;
+ success?: string;
+ cancel?: string;
+};
+
+type CreateCheckoutSessionResponse = {
+ checkoutUrl: string;
+};
+
+type BuyButtonProps = {
+ variant?: 'main' | 'floating' | 'top-nav';
+ floatingClassName?: string;
+};
+
+export function BuyButton(props: BuyButtonProps) {
+ const { variant = 'main', floatingClassName } = props;
+
+ const [isFakeLoading, setIsFakeLoading] = useState(true);
+ const [isLoginPopupOpen, setIsLoginPopupOpen] = useState(false);
+ const [isVideoModalOpen, setIsVideoModalOpen] = useState(false);
+ const toast = useToast();
+
+ const { copyText, isCopied } = useCopyText();
+
+ const { data: coursePricing, isLoading: isLoadingPrice } = useQuery(
+ coursePriceOptions({ courseSlug: SQL_COURSE_SLUG }),
+ queryClient,
+ );
+
+ const { data: courseProgress, isLoading: isLoadingCourseProgress } = useQuery(
+ courseProgressOptions(SQL_COURSE_SLUG),
+ queryClient,
+ );
+
+ const {
+ mutate: createCheckoutSession,
+ isPending: isCreatingCheckoutSession,
+ isSuccess: isCheckoutSessionCreated,
+ } = useMutation(
+ {
+ mutationFn: (body: CreateCheckoutSessionBody) => {
+ return httpPost(
+ '/v1-create-checkout-session',
+ body,
+ );
+ },
+ onMutate: () => {
+ toast.loading('Creating checkout session...');
+ },
+ onSuccess: (data) => {
+ if (!window.gtag) {
+ window.location.href = data.checkoutUrl;
+ return;
+ }
+
+ window?.fireEvent({
+ action: `${SQL_COURSE_SLUG}_begin_checkout`,
+ category: 'course',
+ label: `${SQL_COURSE_SLUG} Course Checkout Started`,
+ callback: () => {
+ window.location.href = data.checkoutUrl;
+ },
+ });
+
+ // Hacky way to make sure that we redirect in case
+ // GA was blocked or not able to redirect the user.
+ setTimeout(() => {
+ window.location.href = data.checkoutUrl;
+ }, 3000);
+ },
+ onError: (error) => {
+ console.error(error);
+ toast.error(error?.message || 'Failed to create checkout session');
+ },
+ },
+ queryClient,
+ );
+
+ useEffect(() => {
+ const urlParams = getUrlParams();
+ const shouldTriggerPurchase = urlParams[COURSE_PURCHASE_PARAM] === '1';
+ 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);
+ initPurchase();
+ }
+ }, []);
+
+ useEffect(() => {
+ const urlParams = getUrlParams();
+ const param = urlParams?.[COURSE_PURCHASE_SUCCESS_PARAM];
+ if (!param) {
+ return;
+ }
+
+ const success = param === '1';
+
+ if (success) {
+ window?.fireEvent({
+ action: `${SQL_COURSE_SLUG}_purchase_complete`,
+ category: 'course',
+ label: `${SQL_COURSE_SLUG} Course Purchase Completed`,
+ });
+ } else {
+ window?.fireEvent({
+ action: `${SQL_COURSE_SLUG}_purchase_canceled`,
+ category: 'course',
+ label: `${SQL_COURSE_SLUG} Course Purchase Canceled`,
+ });
+ }
+
+ deleteUrlParam(COURSE_PURCHASE_SUCCESS_PARAM);
+ }, []);
+
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ setIsFakeLoading(false);
+ }, 500);
+
+ return () => clearTimeout(timer);
+ }, []);
+
+ const isLoadingPricing =
+ isFakeLoading ||
+ isCheckoutSessionCreated ||
+ isLoadingPrice ||
+ !coursePricing ||
+ isLoadingCourseProgress ||
+ isCreatingCheckoutSession;
+ const isAlreadyEnrolled = !!courseProgress?.enrolledAt;
+
+ function initPurchase() {
+ if (!isLoggedIn()) {
+ setIsLoginPopupOpen(true);
+ return;
+ }
+
+ const encodedCourseSlug = encodeURIComponent(`/courses/${SQL_COURSE_SLUG}`);
+ const successUrl = `/thank-you?next=${encodedCourseSlug}`;
+
+ createCheckoutSession({
+ courseId: SQL_COURSE_SLUG,
+ success: successUrl,
+ cancel: `/courses/${SQL_COURSE_SLUG}?${COURSE_PURCHASE_SUCCESS_PARAM}=0`,
+ });
+ }
+
+ function onBuyClick() {
+ if (!isLoggedIn()) {
+ setIsLoginPopupOpen(true);
+ return;
+ }
+
+ const hasEnrolled = !!courseProgress?.enrolledAt;
+ if (hasEnrolled) {
+ window.location.href = `${import.meta.env.PUBLIC_COURSE_APP_URL}/${SQL_COURSE_SLUG}`;
+ return;
+ }
+
+ initPurchase();
+ }
+
+ function onReadSampleClick() {
+ if (!isLoggedIn()) {
+ localStorage.setItem(SAMPLE_AFTER_LOGIN_KEY, '1');
+ setIsLoginPopupOpen(true);
+ return;
+ }
+
+ window?.fireEvent({
+ action: `${SQL_COURSE_SLUG}_demo_started`,
+ category: 'course',
+ label: `${SQL_COURSE_SLUG} Course Demo Started`,
+ });
+
+ setTimeout(() => {
+ window.location.href = `${import.meta.env.PUBLIC_COURSE_APP_URL}/${SQL_COURSE_SLUG}`;
+ }, 200);
+ }
+
+ const courseLoginPopup = isLoginPopupOpen && (
+ setIsLoginPopupOpen(false)} />
+ );
+
+ const mainCouponAlert = (
+
+
+
+
+ 🎁 30% OFF with code{' '}
+ {
+ copyText(sqlCouponCode);
+ }}
+ className="inline-block rounded bg-black px-1.5 py-0.5 font-mono text-white hover:opacity-75"
+ >
+ {sqlCouponCode}
+ {isCopied && (
+
+ )}
+ {!isCopied && (
+
+ )}
+
+
+
+
+ );
+
+ if (variant === 'main') {
+ return (
+ <>
+ {courseLoginPopup}
+
+
+ {isVideoModalOpen && (
+
setIsVideoModalOpen(false)}
+ />
+ )}
+
+ {!isLoadingPricing && !isAlreadyEnrolled && mainCouponAlert}
+
+
+ {isLoadingPricing ? (
+
+ ) : isAlreadyEnrolled ? (
+
+ Start Learning
+
+ ) : (
+
+ Buy now for{' '}
+ {coursePricing?.isEligibleForDiscount ? (
+
+
+ ${coursePricing?.fullPrice}
+
+
+ ${coursePricing?.regionalPrice}
+
+
+ ) : (
+ ${coursePricing?.regionalPrice}
+ )}
+
+
+ )}
+
+
+
+
+ Access Demo
+
+
+
+
+ {!isLoadingPricing && (
+
+ Lifetime access · {' '}
+ setIsVideoModalOpen(true)}
+ className="flex cursor-pointer flex-row items-center gap-1.5 underline underline-offset-4 hover:text-yellow-500"
+ >
+ Watch Video (3 min)
+
+
+ )}
+
+ >
+ );
+ }
+
+ if (variant === 'top-nav') {
+ return (
+
+ Purchase Course
+
+ );
+ }
+
+ return (
+ <>
+ {courseLoginPopup}
+
+
+ {isLoadingPricing ? (
+
+ ) : isAlreadyEnrolled ? (
+
+ Start Learning
+
+ ) : (
+
+ Start learning now
+ Start now - $
+ {coursePricing?.regionalPrice}
+
+
+ )}
+
+
+ >
+ );
+}
diff --git a/src/components/SQLCourseVariant/ChapterRow.tsx b/src/components/SQLCourseVariant/ChapterRow.tsx
new file mode 100644
index 000000000..d54cfa81d
--- /dev/null
+++ b/src/components/SQLCourseVariant/ChapterRow.tsx
@@ -0,0 +1,140 @@
+import { ChevronDown, BookIcon, CodeIcon, CircleDot } from 'lucide-react';
+import { cn } from '../../lib/classname';
+import { useState } from 'react';
+
+type ChapterRowProps = {
+ counter: number;
+ icon: React.ReactNode;
+ title: string;
+ description: string;
+ lessonCount: number;
+ challengeCount: number;
+ isExpandable?: boolean;
+ className?: string;
+ lessons?: { title: string; type: 'lesson' | 'challenge' | 'quiz' }[];
+};
+
+export function ChapterRow(props: ChapterRowProps) {
+ const {
+ counter,
+ icon,
+ title,
+ description,
+ lessonCount,
+ challengeCount,
+ isExpandable = true,
+ className,
+ lessons = [],
+ } = props;
+
+ const [isExpanded, setIsExpanded] = useState(false);
+
+ const regularLessons = lessons.filter((l) => l.type === 'lesson');
+ const challenges = lessons.filter((l) =>
+ ['challenge', 'quiz'].includes(l.type),
+ );
+
+ return (
+
+
isExpandable && setIsExpanded(!isExpanded)}
+ className={cn(
+ 'relative rounded-xl border border-zinc-800 bg-zinc-800 p-6',
+ 'bg-linear-to-br from-zinc-900/90 via-zinc-900/70 to-zinc-900/50',
+ !isExpanded &&
+ 'hover:bg-linear-to-br hover:from-zinc-900/95 hover:via-zinc-900/80 hover:to-zinc-900/60',
+ !isExpanded &&
+ 'hover:cursor-pointer hover:shadow-[0_0_30px_rgba(0,0,0,0.2)]',
+ isExpanded && 'cursor-pointer rounded-b-none border-b-0',
+ )}
+ >
+
+
+
+
+
+
+ {counter}.{' '}
+
+ {title}
+
+
{description}
+
+
+
+ {lessonCount} Lessons
+
+
+ {challengeCount} Challenges
+
+
+
+
+ {isExpandable && (
+
+
+
+ )}
+
+
+
+ {isExpanded && (
+
+
+ {regularLessons.length > 0 && (
+
+
+ Lessons
+
+
+ {regularLessons.map((lesson, index) => (
+
+
+ {lesson.title}
+
+ ))}
+
+
+ )}
+
+ {challenges.length > 0 && (
+
+
+ Exercises
+
+
+ {challenges.map((challenge, index) => (
+
+ {challenge.type === 'challenge' ? (
+
+ ) : (
+
+ )}
+ {challenge.title}
+
+ ))}
+
+
+ )}
+
+
+ )}
+
+ );
+}
diff --git a/src/components/SQLCourseVariant/CourseDiscountBanner.tsx b/src/components/SQLCourseVariant/CourseDiscountBanner.tsx
new file mode 100644
index 000000000..829528ff9
--- /dev/null
+++ b/src/components/SQLCourseVariant/CourseDiscountBanner.tsx
@@ -0,0 +1,79 @@
+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 (
+
+
+ 🎁 Limited time offer :
+
+ Get 30% off using{' '}
+ {
+ 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',
+ )}
+ >
+ Coupon code :
+ {sqlCouponCode}
+ {isCopied && (
+
+ )}
+ {!isCopied && (
+
+ )}
+
+ {
+ 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"
+ >
+
+
+
+ );
+}
diff --git a/src/components/SQLCourseVariant/CourseFeatures.tsx b/src/components/SQLCourseVariant/CourseFeatures.tsx
new file mode 100644
index 000000000..113038c0c
--- /dev/null
+++ b/src/components/SQLCourseVariant/CourseFeatures.tsx
@@ -0,0 +1,149 @@
+import {
+ BookIcon,
+ BrainIcon,
+ ClipboardIcon,
+ CodeIcon,
+ FileCheckIcon,
+ FileQuestionIcon,
+ MinusIcon,
+ PlusIcon,
+} from 'lucide-react';
+import { SectionHeader } from './SectionHeader';
+import { useState } from 'react';
+import { cn } from '../../lib/classname';
+
+type Feature = {
+ title: string;
+ description: string;
+ icon: React.ElementType;
+ imgUrl: string;
+};
+
+export function CourseFeatures() {
+ const features: Feature[] = [
+ {
+ title: 'AI Tutor',
+ description:
+ 'Powerful AI tutor to help you with your queries, provide additional explanations and help if you get stuck.',
+ icon: BrainIcon,
+ imgUrl: 'https://assets.roadmap.sh/guest/ai-integration.png',
+ },
+ {
+ title: 'Real-world Challenges',
+ description:
+ 'The course is packed with practical challenges and quizzes, allowing you to test your knowledge and skills.',
+ icon: FileQuestionIcon,
+ imgUrl: 'https://assets.roadmap.sh/guest/coding-challenges.png',
+ },
+ {
+ title: 'Coding Environment',
+ description:
+ 'With the integrated IDE, you can practice your SQL queries in real-time, getting instant feedback on your results.',
+ icon: CodeIcon,
+ imgUrl: 'https://assets.roadmap.sh/guest/coding-environment.png',
+ },
+ {
+ title: 'Textual Course',
+ description:
+ 'Unlike video-based courses where you have to learn at the pace of the instructor, this course is text-based, allowing you to learn at your own pace.',
+ icon: BookIcon,
+ imgUrl: 'https://assets.roadmap.sh/guest/textual-course.png',
+ },
+ {
+ title: 'Take Notes',
+ description:
+ 'The course allows you to take notes, where you can write down your thoughts and ideas. You can visit them later to review your progress.',
+ icon: ClipboardIcon,
+ imgUrl: 'https://assets.roadmap.sh/guest/course-notes.png',
+ },
+ {
+ title: 'Completion Certificate',
+ description:
+ 'The course provides a completion certificate, which you can share with your potential employers.',
+ icon: FileCheckIcon,
+ imgUrl: 'https://assets.roadmap.sh/guest/course-certificate.jpg',
+ },
+ ];
+
+ const [expandedFeatureIndex, setExpandedFeatureIndex] = useState<
+ number | null
+ >(0);
+
+ return (
+
+
+
+
+ {features.map((feature, index) => (
+
+ setExpandedFeatureIndex(
+ expandedFeatureIndex === index ? null : index,
+ )
+ }
+ />
+ ))}
+
+
+ );
+}
+
+type CourseFeatureProps = Feature & {
+ isExpanded?: boolean;
+ onExpand?: () => void;
+};
+
+function CourseFeature(props: CourseFeatureProps) {
+ const {
+ title,
+ description,
+ icon: Icon,
+ imgUrl,
+ isExpanded,
+ onExpand,
+ } = props;
+
+ return (
+
+
+
+
+
+ {title}
+
+
+
+
+ {isExpanded ? (
+
+ ) : (
+
+ )}
+
+
+
+ {isExpanded && (
+
+
{description}
+
+
+ )}
+
+ );
+}
diff --git a/src/components/SQLCourseVariant/FAQSection.tsx b/src/components/SQLCourseVariant/FAQSection.tsx
new file mode 100644
index 000000000..a1e96b534
--- /dev/null
+++ b/src/components/SQLCourseVariant/FAQSection.tsx
@@ -0,0 +1,115 @@
+import { ChevronDownIcon } from 'lucide-react';
+import { useState } from 'react';
+import { SectionHeader } from './SectionHeader';
+
+type FAQItem = {
+ question: string;
+ answer: string;
+};
+
+function FAQRow({ question, answer }: FAQItem) {
+ const [isExpanded, setIsExpanded] = useState(false);
+
+ return (
+
+
setIsExpanded(!isExpanded)}
+ className="flex w-full items-center justify-between gap-2 p-4 text-left md:p-6"
+ >
+
+ {question}
+
+
+
+ {isExpanded && (
+
+ )}
+
+ );
+}
+
+export function FAQSection() {
+ const faqs: FAQItem[] = [
+ {
+ question: 'What is the format of the course?',
+ answer:
+ 'The course is written in textual format. There are several chapters; each chapter has a set of lessons, followed by a set of practice problems and quizzes. You can learn at your own pace and revisit the content anytime.',
+ },
+ {
+ question: 'What prerequisites do I need for this course?',
+ answer:
+ 'No prior SQL knowledge is required. The course starts from the basics and gradually progresses to advanced topics.',
+ },
+ {
+ question: 'Do I need to have a local database to follow the course?',
+ answer:
+ 'No, we have an integrated coding playground, populated with a sample databases depending on the lesson, that you can use to follow the course. You can also use your own database if you have one.',
+ },
+ {
+ question: 'How long do I have access to the course?',
+ answer:
+ 'You get lifetime access to the course including all future updates. Once you purchase, you can learn at your own pace and revisit the content anytime.',
+ },
+ {
+ question: 'What kind of support is available?',
+ answer:
+ 'You get access to an AI tutor within the course that can help you with queries 24/7. Additionally, you can use the community forums to discuss problems and get help from other learners.',
+ },
+ {
+ question: 'Will I get a certificate upon completion?',
+ answer:
+ "Yes, upon completing the course and its challenges, you'll receive a certificate of completion that you can share with employers or add to your LinkedIn profile.",
+ },
+ {
+ question: 'Can I use this for job interviews?',
+ answer:
+ 'Absolutely! The course covers common SQL interview topics and includes practical challenges similar to what you might face in technical interviews. The hands-on experience will prepare you well for real-world scenarios.',
+ },
+ {
+ question: "What if I don't like the course?",
+ answer:
+ "You can request a refund within 30 days of purchase by emailing info@roadmap.sh. The refund amount will be prorated based on when you request it. For example, if you request a refund 15 days after purchase, you'll receive 50% back. I'd also love to hear your feedback to improve the course.",
+ },
+ {
+ question: 'I already know SQL, can I still take this course?',
+ answer:
+ 'Yes! The course starts from the basics and gradually progresses to advanced topics. You can skip the chapters that you already know and focus on the ones that you need.',
+ },
+ {
+ question: 'Do you offer any team licenses?',
+ answer: 'Yes, please contact me at kamran@roadmap.sh',
+ },
+ {
+ question: 'How can I gift this course to someone?',
+ answer:
+ 'Please contact me at kamran@roadmap.sh and I will be happy to help you.',
+ },
+ {
+ question: 'What if I have a question that is not answered here?',
+ answer:
+ 'Please contact me at kamran@roadmap.sh and I will be happy to help you.',
+ },
+ ];
+
+ return (
+ <>
+
+
+
+ {faqs.map((faq, index) => (
+
+ ))}
+
+ >
+ );
+}
diff --git a/src/components/SQLCourseVariant/MeetYourInstructor.tsx b/src/components/SQLCourseVariant/MeetYourInstructor.tsx
new file mode 100644
index 000000000..d65d6f403
--- /dev/null
+++ b/src/components/SQLCourseVariant/MeetYourInstructor.tsx
@@ -0,0 +1,89 @@
+import { AwardIcon, TrophyIcon } from 'lucide-react';
+import { GitHubIcon } from '../ReactIcons/GitHubIcon';
+
+export function MeetYourInstructor() {
+ const features = [
+ {
+ icon: TrophyIcon,
+ text: 'Multiple GitHub Star Awards'
+ },
+ {
+ icon: GitHubIcon,
+ text: '#2 Most Starred Developer'
+ },
+ {
+ icon: AwardIcon,
+ text: 'Google Developer Expert'
+ },
+ {
+ icon: AwardIcon,
+ text: '2M+ roadmap.sh users'
+ }
+ ];
+
+ return (
+
+
+
+ Meet your instructor
+
+
+ Kamran Ahmed
+
+
+
+
+
+
+
+
+ Kamran Ahmed
+
+
+ Founder of roadmap.sh
+
+
+
+
+
+
+ {features.map((feature, index) => {
+ const IconComponent = feature.icon;
+ return (
+
+
+
+ {feature.text}
+
+
+ );
+ })}
+
+
+
+
+
+
+ Kamran is the creator of roadmap.sh (2M+ registered users!)
+ and an engineering leader with over a decade of experience in
+ the tech industry. Throughout his career he's built and scaled
+ software systems, designed complex data systems, and worked
+ with large amounts of data to create efficient solutions.
+
+
+
+ This hands-on, AI-assisted course is his distilled blueprint
+ on how to master SQL queries, from beginner to advanced.
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/SQLCourseVariant/PlatformDemo.tsx b/src/components/SQLCourseVariant/PlatformDemo.tsx
new file mode 100644
index 000000000..cf0058722
--- /dev/null
+++ b/src/components/SQLCourseVariant/PlatformDemo.tsx
@@ -0,0 +1,33 @@
+import { useState } from 'react';
+import { Play } from 'lucide-react';
+import { VideoModal } from '../VideoModal';
+
+export function PlatformDemo() {
+ const [isVideoModalOpen, setIsVideoModalOpen] = useState(false);
+
+ return (
+ <>
+ {isVideoModalOpen && (
+ setIsVideoModalOpen(false)}
+ />
+ )}
+
+
+
setIsVideoModalOpen(true)}
+ className="group absolute inset-0 flex cursor-pointer items-center justify-center bg-black/40 transition-all hover:bg-black/50"
+ >
+
+
+
+ >
+ );
+}
diff --git a/src/components/SQLCourseVariant/PurchaseBanner.tsx b/src/components/SQLCourseVariant/PurchaseBanner.tsx
new file mode 100644
index 000000000..ab1a9bcbd
--- /dev/null
+++ b/src/components/SQLCourseVariant/PurchaseBanner.tsx
@@ -0,0 +1,85 @@
+import { CheckIcon, Star } from 'lucide-react';
+import { BuyButton } from './BuyButton';
+import { Rating } from '../Rating/Rating';
+import { cn } from '../../lib/classname';
+import { useEffect, useRef, useState } from 'react';
+
+export function PurchaseBanner() {
+ const bannerRef = useRef(null);
+
+ const [isOutOfView, setIsOutOfView] = useState(false);
+
+ useEffect(() => {
+ const handleScroll = () => {
+ if (!bannerRef.current) return;
+
+ const bannerRect = bannerRef.current.getBoundingClientRect();
+ const bannerBottom = bannerRect.bottom;
+
+ // Banner is out of view when its bottom is above the viewport
+ setIsOutOfView(bannerBottom < 0);
+ };
+
+ window.addEventListener('scroll', handleScroll);
+ handleScroll(); // Check initial state
+
+ return () => {
+ window.removeEventListener('scroll', handleScroll);
+ };
+ }, []);
+
+ const Banner = (props: {
+ className?: string;
+ ref?: React.RefObject;
+ }) => {
+ return (
+
+
+ {['7-Day Money-Back Guarantee', 'Lifetime access & updates'].map(
+ (text, index) => (
+
+
+ {text}
+
+ ),
+ )}
+
+
+
+
+
+
+
+
+
+
+ 4.9 avg. Review
+
+
+
+ );
+ };
+ return (
+ <>
+
+
+ >
+ );
+}
diff --git a/src/components/SQLCourseVariant/ReviewCarousel.tsx b/src/components/SQLCourseVariant/ReviewCarousel.tsx
new file mode 100644
index 000000000..6412ad1de
--- /dev/null
+++ b/src/components/SQLCourseVariant/ReviewCarousel.tsx
@@ -0,0 +1,312 @@
+import {
+ ChevronLeftIcon,
+ ChevronRightIcon,
+ StarIcon,
+ User2Icon,
+} from 'lucide-react';
+import { useLayoutEffect, useMemo, useState } from 'react';
+import { markdownToHtml } from '../../lib/markdown';
+import { getTailwindScreenDimension } from '../../lib/is-mobile';
+import { cn } from '../../lib/classname';
+
+type Review = {
+ name: string;
+ role: string;
+ rating: number;
+ text: string | string[];
+ avatarUrl?: string;
+ isProminent?: boolean;
+ isSecondaryProminent?: boolean;
+ companyName?: string;
+ companyLogo?: string;
+};
+
+export function ReviewCarousel() {
+ const reviews: Review[] = [
+ {
+ companyName: 'Cloud Camping',
+ companyLogo: 'https://assets.roadmap.sh/guest/cloudcamping-mcpl6.jpeg',
+ name: 'Robin Wieruch',
+ role: 'Author - Multiple Best-Sellers',
+ rating: 5,
+ text: [
+ 'Kamran has been in the **educative space for a long time**, and it shows in the way he teaches SQL: clear, structured, and straight to the point.',
+ "Even if you've used SQL before, this **course will fill in gaps you didn't even realize you had**. Get ready to level up your database skills!",
+ ],
+ avatarUrl: 'https://assets.roadmap.sh/guest/robin.jpeg',
+ isProminent: true,
+ },
+ {
+ companyName: 'Hack Mamba',
+ companyLogo: 'https://assets.roadmap.sh/guest/hackmbamba-h0ivr.jpeg',
+ name: 'William Imoh',
+ role: 'Founder and Data Enthusiast',
+ rating: 5,
+ text: [
+ 'I bought this course for the advanced chapters but ended up completing the entire course. I learned a lot of new things and it was **well worth the investment**.',
+ 'No matter your SQL experience, this course is **a must-have** if you want to level up your SQL and data analysis skills. Highly recommended!',
+ ],
+ avatarUrl: 'https://assets.roadmap.sh/guest/william-imoh-sd2dk.jpg',
+ isProminent: true,
+ },
+ {
+ companyName: 'GlobalLogic',
+ companyLogo:
+ 'https://assets.roadmap.sh/guest/globallogic_logo-3m3ho.jpeg',
+ name: 'Martina Milagros',
+ role: 'Software Engineer',
+ rating: 5,
+ text: [
+ 'Thanks to Kamran Ahmed for the **incredible Master SQL course!** I truly appreciate the way you break down complex topics with such clarity and ease.',
+ '**Highly recommend this course** to anyone looking to level up their SQL game!',
+ ],
+ avatarUrl: 'https://assets.roadmap.sh/guest/martina-awc4x.jpeg',
+ },
+ {
+ companyName: 'Cisco',
+ companyLogo: 'https://assets.roadmap.sh/guest/cisco-gyw5b.jpeg',
+ name: 'Tomáš Janků',
+ role: 'Sr. Software Engineer',
+ rating: 5,
+ text: "The course and it's interactivity is excellent and I'd honestly say it's **one of the best** on the SQL theme I've seen out there.",
+ avatarUrl: 'https://assets.roadmap.sh/guest/tomas-janku-6bg89.jpeg',
+ },
+ {
+ companyName: 'Beyond Works',
+ companyLogo:
+ 'https://assets.roadmap.sh/guest/beyondwordsio_logo-xia4m.jpeg',
+ name: 'Gourav Khunger',
+ role: 'Software Engineer',
+ rating: 5,
+ text: [
+ 'This course was **absolutely brilliant!** The integrated database environment to practice what I learned was the best part. Being able to **run queries immediately** and see results in real-time made everything click so much faster than traditional learning methods.',
+ ],
+ avatarUrl: 'https://assets.roadmap.sh/guest/gourav-h2f3a.png',
+ },
+ {
+ companyName: 'xpertSea',
+ companyLogo: 'https://assets.roadmap.sh/guest/xpertsea-y24hu.jpeg',
+ name: 'Meabed',
+ role: 'CTO',
+ rating: 5,
+ text: 'Kamran has **clearly put a lot of thought** into this course. The content, structure and exercises were all great.',
+ avatarUrl: 'https://assets.roadmap.sh/guest/meabed-fu83q.jpeg',
+ },
+ {
+ companyName: 'Powersoft19',
+ companyLogo: 'https://assets.roadmap.sh/guest/powersoft19-sk4t1.jpeg',
+ name: 'Mohsin Aheer',
+ role: 'Sr. Software Engineer',
+ rating: 5,
+ text: 'I already knew SQL but this course **taught me a bunch of new things.** Practical examples and challenges were great. Highly recommended!',
+ avatarUrl: 'https://assets.roadmap.sh/guest/mohsinaheer-szchu.jpeg',
+ },
+ {
+ companyName: 'xpertSea',
+ companyLogo: 'https://assets.roadmap.sh/guest/xpertsea-y24hu.jpeg',
+ name: 'Zeeshan',
+ role: 'Sr. Software Engineer',
+ rating: 5,
+ text: 'Loved the teaching style and the way the course was structured. The **AI tutor was a great help** when I wanted some extra help.',
+ avatarUrl: 'https://assets.roadmap.sh/guest/ziishaned-qjepj.png',
+ },
+ {
+ companyName: 'University of Regensburg',
+ companyLogo:
+ 'https://assets.roadmap.sh/guest/university_of_regensburg_logo-01784.jpeg',
+ name: 'Faisal Ahsan',
+ role: 'Software Engineer',
+ rating: 5,
+ text: 'The course and the learning experience was great. What I really liked was the **no-fluff explanations** and **practical examples**.',
+ avatarUrl: 'https://assets.roadmap.sh/guest/faisal-q78p2.jpeg',
+ },
+ {
+ companyName: 'xpertSea',
+ companyLogo: 'https://assets.roadmap.sh/guest/xpertsea-y24hu.jpeg',
+ name: 'Adnan Ahmed',
+ role: 'Engineering Manager',
+ rating: 5,
+ text: 'Having the integrated IDE made a huge difference. Being able to **immediately practice** what I learned was **invaluable**.',
+ avatarUrl: 'https://assets.roadmap.sh/guest/idnan-fzps5.jpeg',
+ },
+ {
+ name: 'Kalvin Chakma',
+ role: 'Jr. Software Engineer',
+ rating: 5,
+ text: "Best SQL course I've taken. The progression from basic to advanced concepts is **well thought out**, and the challenges are **excellent**.",
+ avatarUrl: 'https://assets.roadmap.sh/guest/kalvin-d65ol.jpeg',
+ },
+ ];
+
+ const [batchSize, setBatchSize] = useState(3);
+ const maxBatchNumber = Math.ceil(reviews.length / batchSize);
+ const [currentBatchNumber, setCurrentBatchNumber] = useState(0);
+
+ const currentBatch = useMemo(() => {
+ const result = reviews.slice(
+ currentBatchNumber * batchSize,
+ (currentBatchNumber + 1) * batchSize,
+ );
+
+ if (result.length < batchSize) {
+ const remaining = batchSize - result.length;
+ return [...result, ...reviews.slice(0, remaining)];
+ }
+
+ return result;
+ }, [currentBatchNumber, batchSize]);
+
+ const handleNextBatch = () => {
+ setCurrentBatchNumber((prev) => (prev + 1) % maxBatchNumber);
+ };
+
+ const handlePreviousBatch = () => {
+ setCurrentBatchNumber(
+ (prev) => (prev - 1 + maxBatchNumber) % maxBatchNumber,
+ );
+ };
+
+ useLayoutEffect(() => {
+ const size = getTailwindScreenDimension();
+
+ if (size === '2xl') {
+ setBatchSize(3);
+ } else if (size === 'xl' || size === 'lg') {
+ setBatchSize(2);
+ } else {
+ setBatchSize(1);
+ }
+ }, []);
+
+ return (
+
+
+ What other learners said
+
+
+
+ }
+ />
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+ {currentBatch.map((review, index) => (
+
+
+
+ {review.avatarUrl && (
+
+ )}
+ {!review.avatarUrl && (
+
+
+
+ )}
+
+
+ {review.name}
+
+
{review.role}
+
+ {Array.from({ length: review.rating }).map((_, i) => (
+
+ ))}
+
+
+
+
+
+ {(typeof review.text === 'string'
+ ? [review.text]
+ : review.text
+ ).map((text, index) => (
+
+ ))}
+
+
+ {review?.companyName && (
+
+ {review?.companyLogo && (
+
+ )}
+
+ {review?.companyName}
+
+
+ )}
+
+ ))}
+
+
+
+
+ }
+ />
+
+
+
+ );
+}
+
+type NavigateButtonProps = {
+ onClick: () => void;
+ icon: React.ReactNode;
+};
+
+function NavigateButton(props: NavigateButtonProps) {
+ const { onClick, icon } = props;
+
+ return (
+
+ {icon}
+
+ );
+}
diff --git a/src/components/SQLCourseVariant/RoadmapDetailsPopover.tsx b/src/components/SQLCourseVariant/RoadmapDetailsPopover.tsx
new file mode 100644
index 000000000..319a7afdc
--- /dev/null
+++ b/src/components/SQLCourseVariant/RoadmapDetailsPopover.tsx
@@ -0,0 +1,27 @@
+import { InfoIcon } from 'lucide-react';
+import { Popover, PopoverContent, PopoverTrigger } from '../Popover';
+
+export function RoadmapDetailsPopover() {
+ return (
+
+
+
+
+
+
+ roadmap.sh
+ {' '}
+ provides community-curated roadmaps, study plans, paths, and resources
+ for developers and IT professionals. Serving 2M+ registered users, it is
+ the 6th most-starred open source project on GitHub
+
+
+ );
+}
diff --git a/src/components/SQLCourseVariant/SQLCourseVariantPage.tsx b/src/components/SQLCourseVariant/SQLCourseVariantPage.tsx
new file mode 100644
index 000000000..bf6aeb12b
--- /dev/null
+++ b/src/components/SQLCourseVariant/SQLCourseVariantPage.tsx
@@ -0,0 +1,125 @@
+import {
+ BrainIcon,
+ CodeIcon,
+ FileQuestionIcon,
+ NotebookTextIcon,
+} from 'lucide-react';
+import { Spotlight } from '../SQLCourse/Spotlight';
+import { RoadmapLogoIcon } from '../ReactIcons/RoadmapLogo';
+import { AuthorCredentials } from './AuthorCredentials';
+import { PlatformDemo } from './PlatformDemo';
+import { PurchaseBanner } from './PurchaseBanner';
+import { ReviewCarousel } from './ReviewCarousel';
+import { CourseFeatures } from './CourseFeatures';
+import { MeetYourInstructor } from './MeetYourInstructor';
+import { SectionHeader } from './SectionHeader';
+import { ChapterRow } from './ChapterRow';
+import { BuyButton } from './BuyButton';
+import { FAQSection } from './FAQSection';
+import { sqlCourseChapters } from '../SQLCourse/SQLCoursePage';
+
+export function SQLCourseVariantPage() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ Master SQL Queries
+
+
+ Complete course with AI Tutor, real-world challenges and more
+
+
+
+
+
+ Get certified for SQL queries and ready to deploy your newly-gained
+ skill in 30 days. Perfect for developers, data analysts, and anyone
+ working with data. Level up risk-free with a 7-day money-back
+ guarantee.
+
+
+
+
+
+ {[
+ { Icon: NotebookTextIcon, text: '55+ Lessons' },
+ { Icon: FileQuestionIcon, text: '100+ Challenges' },
+ { Icon: BrainIcon, text: 'AI Tutor' },
+ { Icon: CodeIcon, text: 'Integrated IDE' },
+ ].map(({ Icon, text }, index) => (
+
+
+ {text}
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {sqlCourseChapters.map((chapter, index) => (
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/SQLCourseVariant/SectionHeader.tsx b/src/components/SQLCourseVariant/SectionHeader.tsx
new file mode 100644
index 000000000..ff2658757
--- /dev/null
+++ b/src/components/SQLCourseVariant/SectionHeader.tsx
@@ -0,0 +1,28 @@
+import { cn } from '../../lib/classname';
+
+type SectionHeaderProps = {
+ title: string;
+ description: string | React.ReactNode;
+ className?: string;
+};
+
+export function SectionHeader(props: SectionHeaderProps) {
+ const { title, description, className } = props;
+
+ return (
+
+
+
+ {title}
+
+
+ {typeof description === 'string' ? (
+
+ {description}
+
+ ) : (
+ description
+ )}
+
+ );
+}
diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro
index c31e39368..e4012d868 100644
--- a/src/layouts/BaseLayout.astro
+++ b/src/layouts/BaseLayout.astro
@@ -156,7 +156,7 @@ const gaPageIdentifier = Astro.url.pathname
{
- hasVarify && (
+ hasVarify && !import.meta.env.DEV && (