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 ( +
+ Kamran Ahmed + +
+
+

+ 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{' '} + + +
+
+ ); + + if (variant === 'main') { + return ( + <> + {courseLoginPopup} + +
+ {isVideoModalOpen && ( + setIsVideoModalOpen(false)} + /> + )} +
+ {!isLoadingPricing && !isAlreadyEnrolled && mainCouponAlert} + + + +
+ + {!isLoadingPricing && ( + + Lifetime access ·{' '} + + + )} +
+ + ); + } + + if (variant === 'top-nav') { + return ( + + ); + } + + return ( + <> + {courseLoginPopup} +
+ +
+ + ); +} 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', + )} + > +
+
+
{icon}
+
+ +
+

+ + {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{' '} + + +
+ ); +} 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 ( +
+ + + {isExpanded && ( +
+

{description}

+ {title} +
+ )} +
+ ); +} 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 ( +
+ + {isExpanded && ( +
+

{answer}

+
+ )} +
+ ); +} + +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 +
+
+ 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)} + /> + )} +
+ Course Environment +
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.name} + )} + {!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} + )} +
+ {review?.companyName} +
+
+ )} +
+ ))} +
+ +
+ + } + /> +
+
+
+ ); +} + +type NavigateButtonProps = { + onClick: () => void; + icon: React.ReactNode; +}; + +function NavigateButton(props: NavigateButtonProps) { + const { onClick, icon } = props; + + return ( + + ); +} 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 && (