diff --git a/src/components/GenerateCourse/AICourseContent.tsx b/src/components/GenerateCourse/AICourseContent.tsx index d182be30a..9e0c4564c 100644 --- a/src/components/GenerateCourse/AICourseContent.tsx +++ b/src/components/GenerateCourse/AICourseContent.tsx @@ -1,28 +1,25 @@ -import { useQuery } from '@tanstack/react-query'; import { BookOpenCheck, ChevronLeft, + CircleAlert, Loader2, Menu, - X, - CircleAlert, Play, + X, } from 'lucide-react'; import { useState } from 'react'; import { type AiCourse } from '../../lib/ai'; import { cn } from '../../lib/classname'; import { slugify } from '../../lib/slugger'; -import { getAiCourseProgressOptions } from '../../queries/ai-course'; -import { queryClient } from '../../stores/query-client'; +import { useIsPaidUser } from '../../queries/billing'; +import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal'; import { CheckIcon } from '../ReactIcons/CheckIcon'; import { ErrorIcon } from '../ReactIcons/ErrorIcon'; +import { AICourseLesson } from './AICourseLesson'; import { AICourseLimit } from './AICourseLimit'; import { AICourseSidebarModuleList } from './AICourseSidebarModuleList'; -import { AICourseLesson } from './AICourseLesson'; -import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal'; import { AILimitsPopup } from './AILimitsPopup'; import { RegenerateOutline } from './RegenerateOutline'; -import { useIsPaidUser } from '../../queries/billing'; type AICourseContentProps = { courseSlug?: string; @@ -45,10 +42,7 @@ export function AICourseContent(props: AICourseContentProps) { const { isPaidUser } = useIsPaidUser(); - const { data: aiCourseProgress } = useQuery( - getAiCourseProgressOptions({ aiCourseSlug: courseSlug || '' }), - queryClient, - ); + const aiCourseProgress = course.done || []; const [expandedModules, setExpandedModules] = useState< Record<number, boolean> @@ -121,7 +115,7 @@ export function AICourseContent(props: AICourseContentProps) { 0, ); - const totalDoneLessons = aiCourseProgress?.done?.length || 0; + const totalDoneLessons = (course?.done || []).length; const finishedPercentage = Math.round( (totalDoneLessons / totalCourseLessons) * 100, ); @@ -389,6 +383,7 @@ export function AICourseContent(props: AICourseContentProps) { {viewMode === 'module' && ( <AICourseLesson courseSlug={courseSlug!} + progress={aiCourseProgress} activeModuleIndex={activeModuleIndex} totalModules={totalModules} currentModuleTitle={currentModule?.title || ''} @@ -438,9 +433,8 @@ export function AICourseContent(props: AICourseContentProps) { </h2> <div className="divide-y divide-gray-100"> {courseModule.lessons.map((lesson, lessonIdx) => { - const key = `${slugify(courseModule.title)}__${slugify(lesson)}`; - const isCompleted = - aiCourseProgress?.done.includes(key); + const key = `${slugify(String(moduleIdx))}-${slugify(String(lessonIdx))}`; + const isCompleted = aiCourseProgress.includes(key); return ( <div diff --git a/src/components/GenerateCourse/AICourseLesson.tsx b/src/components/GenerateCourse/AICourseLesson.tsx index 8b4418261..b0e9fd364 100644 --- a/src/components/GenerateCourse/AICourseLesson.tsx +++ b/src/components/GenerateCourse/AICourseLesson.tsx @@ -1,4 +1,4 @@ -import { useMutation, useQuery } from '@tanstack/react-query'; +import { useMutation } from '@tanstack/react-query'; import { CheckIcon, ChevronLeft, @@ -8,6 +8,7 @@ import { XIcon, } from 'lucide-react'; import { useEffect, useMemo, useState } from 'react'; +import type { AICourseDocument } from '../../api/ai-roadmap'; import { readStream } from '../../lib/ai'; import { cn } from '../../lib/classname'; import { isLoggedIn, removeAuthToken } from '../../lib/jwt'; @@ -19,16 +20,16 @@ import { httpPatch } from '../../lib/query-http'; import { slugify } from '../../lib/slugger'; import { getAiCourseLimitOptions, - getAiCourseProgressOptions, - type AICourseProgressDocument, + getAiCourseOptions } from '../../queries/ai-course'; +import { useIsPaidUser } from '../../queries/billing'; import { queryClient } from '../../stores/query-client'; import { AICourseFollowUp } from './AICourseFollowUp'; import './AICourseFollowUp.css'; -import { useIsPaidUser } from '../../queries/billing'; type AICourseLessonProps = { courseSlug: string; + progress: string[]; activeModuleIndex: number; totalModules: number; @@ -46,6 +47,7 @@ type AICourseLessonProps = { export function AICourseLesson(props: AICourseLessonProps) { const { courseSlug, + progress = [], activeModuleIndex, totalModules, @@ -65,13 +67,9 @@ export function AICourseLesson(props: AICourseLessonProps) { const [error, setError] = useState(''); const [lessonHtml, setLessonHtml] = useState(''); - const { data: aiCourseProgress } = useQuery( - getAiCourseProgressOptions({ aiCourseSlug: courseSlug || '' }), - queryClient, - ); - const lessonId = `${slugify(currentModuleTitle)}__${slugify(currentLessonTitle)}`; - const isLessonDone = aiCourseProgress?.done.includes(lessonId); + const lessonId = `${slugify(String(activeModuleIndex))}-${slugify(String(activeLessonIndex))}`; + const isLessonDone = progress?.includes(lessonId); const { isPaidUser } = useIsPaidUser(); @@ -107,11 +105,8 @@ export function AICourseLesson(props: AICourseLessonProps) { signal: abortController.signal, credentials: 'include', body: JSON.stringify({ - moduleTitle: currentModuleTitle, - lessonTitle: currentLessonTitle, - modulePosition: activeModuleIndex, - lessonPosition: activeLessonIndex, - totalLessonsInModule: totalLessons, + moduleIndex: activeModuleIndex, + lessonIndex: activeLessonIndex, }), }, ); @@ -167,16 +162,17 @@ export function AICourseLesson(props: AICourseLessonProps) { const { mutate: toggleDone, isPending: isTogglingDone } = useMutation( { mutationFn: () => { - return httpPatch<AICourseProgressDocument>( + return httpPatch<AICourseDocument>( `/v1-toggle-done-ai-lesson/${courseSlug}`, { - lessonId, + moduleIndex: activeModuleIndex, + lessonIndex: activeLessonIndex, }, ); }, onSuccess: (data) => { queryClient.setQueryData( - ['ai-course-progress', { aiCourseSlug: courseSlug }], + getAiCourseOptions({ aiCourseSlug: courseSlug }).queryKey, data, ); }, diff --git a/src/components/GenerateCourse/AICourseSidebarModuleList.tsx b/src/components/GenerateCourse/AICourseSidebarModuleList.tsx index 7bbf557ee..5158f91b8 100644 --- a/src/components/GenerateCourse/AICourseSidebarModuleList.tsx +++ b/src/components/GenerateCourse/AICourseSidebarModuleList.tsx @@ -2,9 +2,6 @@ import { type Dispatch, type SetStateAction } from 'react'; import type { AiCourse } from '../../lib/ai'; import { Check, ChevronDownIcon, ChevronRightIcon } from 'lucide-react'; import { cn } from '../../lib/classname'; -import { getAiCourseProgressOptions } from '../../queries/ai-course'; -import { useQuery } from '@tanstack/react-query'; -import { queryClient } from '../../stores/query-client'; import { slugify } from '../../lib/slugger'; import { CheckIcon } from '../ReactIcons/CheckIcon'; import { CircularProgress } from './CircularProgress'; @@ -44,10 +41,7 @@ export function AICourseSidebarModuleList(props: AICourseModuleListProps) { isLoading, } = props; - const { data: aiCourseProgress } = useQuery( - getAiCourseProgressOptions({ aiCourseSlug: courseSlug || '' }), - queryClient, - ); + const aiCourseProgress = course.done || []; const toggleModule = (index: number) => { setExpandedModules((prev) => { @@ -71,16 +65,18 @@ export function AICourseSidebarModuleList(props: AICourseModuleListProps) { }); }; - const { done = [] } = aiCourseProgress || {}; + const done = aiCourseProgress || []; return ( <nav className="bg-gray-100"> {course.modules.map((courseModule, moduleIdx) => { const totalLessons = courseModule.lessons.length; - const completedLessons = courseModule.lessons.filter((lesson) => { - const key = `${slugify(courseModule.title)}__${slugify(lesson)}`; - return done.includes(key); - }).length; + const completedLessons = courseModule.lessons.filter( + (lesson, lessonIdx) => { + const key = `${slugify(String(moduleIdx))}-${slugify(String(lessonIdx))}`; + return done.includes(key); + }, + ).length; const percentage = Math.round((completedLessons / totalLessons) * 100); const isActive = expandedModules[moduleIdx]; @@ -139,7 +135,7 @@ export function AICourseSidebarModuleList(props: AICourseModuleListProps) { {expandedModules[moduleIdx] && ( <div className="flex flex-col border-b border-b-gray-200 bg-gray-100"> {courseModule.lessons.map((lesson, lessonIdx) => { - const key = `${slugify(courseModule.title)}__${slugify(lesson)}`; + const key = `${slugify(String(moduleIdx))}-${slugify(String(lessonIdx))}`; const isCompleted = done.includes(key); return ( @@ -160,7 +156,7 @@ export function AICourseSidebarModuleList(props: AICourseModuleListProps) { setViewMode('module'); }} className={cn( - 'flex gap-2.5 w-full cursor-pointer items-center py-3 pl-3.5 pr-2 text-left text-sm leading-normal', + 'flex w-full cursor-pointer items-center gap-2.5 py-3 pl-3.5 pr-2 text-left text-sm leading-normal', activeModuleIndex === moduleIdx && activeLessonIndex === lessonIdx ? 'bg-gray-200 text-black' diff --git a/src/components/GenerateCourse/GenerateAICourse.tsx b/src/components/GenerateCourse/GenerateAICourse.tsx index 7a054a3cb..5b889eac4 100644 --- a/src/components/GenerateCourse/GenerateAICourse.tsx +++ b/src/components/GenerateCourse/GenerateAICourse.tsx @@ -20,6 +20,7 @@ export function GenerateAICourse(props: GenerateAICourseProps) { title: '', modules: [], difficulty: '', + done: [], }); useEffect(() => { diff --git a/src/components/GenerateCourse/GetAICourse.tsx b/src/components/GenerateCourse/GetAICourse.tsx index b3970d3fd..0e113d6b3 100644 --- a/src/components/GenerateCourse/GetAICourse.tsx +++ b/src/components/GenerateCourse/GetAICourse.tsx @@ -1,8 +1,5 @@ import { useQuery } from '@tanstack/react-query'; -import { - getAiCourseOptions, - getAiCourseProgressOptions, -} from '../../queries/ai-course'; +import { getAiCourseOptions } from '../../queries/ai-course'; import { queryClient } from '../../stores/query-client'; import { useEffect, useState } from 'react'; import { AICourseContent } from './AICourseContent'; @@ -24,12 +21,6 @@ export function GetAICourse(props: GetAICourseProps) { const { data: aiCourse, error: queryError } = useQuery( { ...getAiCourseOptions({ aiCourseSlug: courseSlug }), - select: (data) => { - return { - ...data, - course: generateAiCourseStructure(data.data), - }; - }, enabled: !!courseSlug && !!isLoggedIn(), }, queryClient, @@ -75,18 +66,14 @@ export function GetAICourse(props: GetAICourseProps) { ...aiCourse, title: course.title, difficulty: course.difficulty, - data: rawData, + modules: course.modules, }, ); }, onLoadingChange: (isNewLoading) => { setIsRegenerating(isNewLoading); if (!isNewLoading) { - queryClient.invalidateQueries({ - queryKey: getAiCourseProgressOptions({ - aiCourseSlug: courseSlug, - }).queryKey, - }); + // TODO: Update progress } }, onError: setError, @@ -98,8 +85,9 @@ export function GetAICourse(props: GetAICourseProps) { <AICourseContent course={{ title: aiCourse?.title || '', - modules: aiCourse?.course.modules || [], + modules: aiCourse?.modules || [], difficulty: aiCourse?.difficulty || 'Easy', + done: aiCourse?.done || [], }} isLoading={isLoading || isRegenerating} courseSlug={courseSlug} diff --git a/src/helper/generate-ai-course.ts b/src/helper/generate-ai-course.ts index 9aa03ad56..6adab7e9c 100644 --- a/src/helper/generate-ai-course.ts +++ b/src/helper/generate-ai-course.ts @@ -39,6 +39,7 @@ export async function generateCourse(options: GenerateCourseOptions) { title: '', modules: [], difficulty: '', + done: [], }, '', ); diff --git a/src/lib/ai.ts b/src/lib/ai.ts index b7cec3f57..f4f895d73 100644 --- a/src/lib/ai.ts +++ b/src/lib/ai.ts @@ -11,6 +11,7 @@ export type AiCourse = { title: string; modules: Module[]; difficulty: string; + done: string[]; }; export function generateAiCourseStructure( diff --git a/src/queries/ai-course.ts b/src/queries/ai-course.ts index c95c77154..8604a2d4f 100644 --- a/src/queries/ai-course.ts +++ b/src/queries/ai-course.ts @@ -11,24 +11,11 @@ export interface AICourseProgressDocument { updatedAt: Date; } -type GetAICourseProgressParams = { - aiCourseSlug: string; +type AICourseModule = { + title: string; + lessons: string[]; }; -type GetAICourseProgressResponse = AICourseProgressDocument; - -export function getAiCourseProgressOptions(params: GetAICourseProgressParams) { - return { - queryKey: ['ai-course-progress', params], - queryFn: () => { - return httpGet<GetAICourseProgressResponse>( - `/v1-get-ai-course-progress/${params.aiCourseSlug}`, - ); - }, - enabled: !!params.aiCourseSlug && isLoggedIn(), - }; -} - type GetAICourseParams = { aiCourseSlug: string; }; @@ -41,7 +28,7 @@ export interface AICourseDocument { keyword: string; done: string[]; difficulty: string; - data: string; + modules: AICourseModule[]; viewCount: number; createdAt: Date; updatedAt: Date;