diff --git a/src/components/GenerateCourse/GenerateAICourse.tsx b/src/components/GenerateCourse/GenerateAICourse.tsx index a4af327bf..7041bd0bf 100644 --- a/src/components/GenerateCourse/GenerateAICourse.tsx +++ b/src/components/GenerateCourse/GenerateAICourse.tsx @@ -54,6 +54,7 @@ export function GenerateAICourse(props: GenerateAICourseProps) { await generateCourse({ term, difficulty, + slug: courseSlug, onCourseIdChange: setCourseId, onCourseSlugChange: setCourseSlug, onCourseChange: setCourse, diff --git a/src/components/GenerateCourse/GetAICourse.tsx b/src/components/GenerateCourse/GetAICourse.tsx index 5eb38999f..14296e177 100644 --- a/src/components/GenerateCourse/GetAICourse.tsx +++ b/src/components/GenerateCourse/GetAICourse.tsx @@ -15,6 +15,8 @@ export function GetAICourse(props: GetAICourseProps) { const { courseSlug } = props; const [isLoading, setIsLoading] = useState(true); + const [isRegenerating, setIsRegenerating] = useState(false); + const [error, setError] = useState(''); const { data: aiCourse, error: queryError } = useQuery( { @@ -61,7 +63,17 @@ export function GetAICourse(props: GetAICourseProps) { await generateCourse({ term: aiCourse.keyword, difficulty: aiCourse.difficulty, - onLoadingChange: setIsLoading, + slug: courseSlug, + onCourseChange: (course, rawData) => { + queryClient.setQueryData( + getAiCourseOptions({ aiCourseSlug: courseSlug }).queryKey, + { + ...aiCourse, + data: rawData, + }, + ); + }, + onLoadingChange: setIsRegenerating, onError: setError, isForce: true, }); @@ -74,7 +86,7 @@ export function GetAICourse(props: GetAICourseProps) { modules: aiCourse?.course.modules || [], difficulty: aiCourse?.difficulty || 'Easy', }} - isLoading={isLoading} + isLoading={isLoading || isRegenerating} courseSlug={courseSlug} error={error} onRegenerateOutline={handleRegenerateCourse} diff --git a/src/components/GenerateCourse/RegenerateOutline.tsx b/src/components/GenerateCourse/RegenerateOutline.tsx index 0bce70b0e..43616ac9a 100644 --- a/src/components/GenerateCourse/RegenerateOutline.tsx +++ b/src/components/GenerateCourse/RegenerateOutline.tsx @@ -16,7 +16,7 @@ export function RegenerateOutline(props: RegenerateOutlineProps) { const [showUpgradeModal, setShowUpgradeModal] = useState(false); const ref = useRef(null); - const isPaidUser = useIsPaidUser(); + const { isPaidUser } = useIsPaidUser(); useOutsideClick(ref, () => setIsDropdownVisible(false)); diff --git a/src/components/GenerateCourse/UserCoursesList.tsx b/src/components/GenerateCourse/UserCoursesList.tsx index 925c882f5..92ecab6f7 100644 --- a/src/components/GenerateCourse/UserCoursesList.tsx +++ b/src/components/GenerateCourse/UserCoursesList.tsx @@ -10,7 +10,7 @@ import { Gift, Loader2, Search, User2 } from 'lucide-react'; import { isLoggedIn } from '../../lib/jwt'; import { showLoginPopup } from '../../lib/popup'; import { cn } from '../../lib/classname'; -import { billingDetailsOptions } from '../../queries/billing'; +import { useIsPaidUser } from '../../queries/billing'; import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal'; type UserCoursesListProps = {}; @@ -20,17 +20,13 @@ export function UserCoursesList(props: UserCoursesListProps) { const [isInitialLoading, setIsInitialLoading] = useState(true); const [showUpgradePopup, setShowUpgradePopup] = useState(false); - const { data: limits, isLoading } = useQuery( + const { data: limits, isLoading: isLimitsLoading } = useQuery( getAiCourseLimitOptions(), queryClient, ); const { used, limit } = limits ?? { used: 0, limit: 0 }; - - const { data: userBillingDetails, isLoading: isBillingDetailsLoading } = - useQuery(billingDetailsOptions(), queryClient); - - const isPaidUser = userBillingDetails?.status !== 'active'; + const { isPaidUser, isLoading: isPaidUserLoading } = useIsPaidUser(); const { data: userAiCourses, isFetching: isUserAiCoursesLoading } = useQuery( listUserAiCoursesOptions(), @@ -55,13 +51,6 @@ export function UserCoursesList(props: UserCoursesListProps) { }); const isAuthenticated = isLoggedIn(); - - const canSearch = - !isInitialLoading && - !isUserAiCoursesLoading && - isAuthenticated && - userAiCourses?.length !== 0; - const limitUsedPercentage = Math.round((used / limit) * 100); return ( @@ -72,37 +61,39 @@ export function UserCoursesList(props: UserCoursesListProps) {

- Your Courses + Your Courses

-
-

- - {limitUsedPercentage}% of daily limit used{' '} - - - {limitUsedPercentage}% used - - -

-
+ {used > 0 && limit > 0 && !isPaidUserLoading && ( +
+

+ + {limitUsedPercentage}% of daily limit used{' '} + + + {limitUsedPercentage}% used + + +

+
+ )}
diff --git a/src/helper/generate-ai-course.ts b/src/helper/generate-ai-course.ts index 34092fbb1..9706b42e6 100644 --- a/src/helper/generate-ai-course.ts +++ b/src/helper/generate-ai-course.ts @@ -9,10 +9,11 @@ import { getAiCourseLimitOptions } from '../queries/ai-course'; type GenerateCourseOptions = { term: string; difficulty: string; + slug?: string; isForce?: boolean; onCourseIdChange?: (courseId: string) => void; onCourseSlugChange?: (courseSlug: string) => void; - onCourseChange?: (course: AiCourse) => void; + onCourseChange?: (course: AiCourse, rawData: string) => void; onLoadingChange?: (isLoading: boolean) => void; onError?: (error: string) => void; }; @@ -20,6 +21,7 @@ type GenerateCourseOptions = { export async function generateCourse(options: GenerateCourseOptions) { const { term, + slug, difficulty, onCourseIdChange, onCourseSlugChange, @@ -30,29 +32,47 @@ export async function generateCourse(options: GenerateCourseOptions) { } = options; onLoadingChange?.(true); - onCourseChange?.({ - title: '', - modules: [], - difficulty: '', - }); + onCourseChange?.( + { + title: '', + modules: [], + difficulty: '', + }, + '', + ); onError?.(''); try { - const response = await fetch( - `${import.meta.env.PUBLIC_API_URL}/v1-generate-ai-course`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', + let response = null; + + if (slug && isForce) { + response = await fetch( + `${import.meta.env.PUBLIC_API_URL}/v1-regenerate-ai-course/${slug}`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', }, - body: JSON.stringify({ - keyword: term, - difficulty, - isForce, - }), - credentials: 'include', - }, - ); + ); + } else { + response = await fetch( + `${import.meta.env.PUBLIC_API_URL}/v1-generate-ai-course`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + keyword: term, + difficulty, + isForce, + }), + credentials: 'include', + }, + ); + } if (!response.ok) { const data = await response.json(); @@ -108,10 +128,13 @@ export async function generateCourse(options: GenerateCourseOptions) { try { const aiCourse = generateAiCourseStructure(result); - onCourseChange?.({ - ...aiCourse, - difficulty: difficulty || '', - }); + onCourseChange?.( + { + ...aiCourse, + difficulty: difficulty || '', + }, + result, + ); } catch (e) { console.error('Error parsing streamed course content:', e); } diff --git a/src/helper/number.ts b/src/helper/number.ts deleted file mode 100644 index 5e1309319..000000000 --- a/src/helper/number.ts +++ /dev/null @@ -1,12 +0,0 @@ -export function getPercentage(portion: number, total: number): number { - if (portion <= 0 || total <= 0) { - return 0; - } - - if (portion >= total) { - return 100; - } - - const percentage = (portion / total) * 100; - return Math.round(percentage); -} diff --git a/src/helper/read-stream.ts b/src/helper/read-stream.ts deleted file mode 100644 index 8ae446cae..000000000 --- a/src/helper/read-stream.ts +++ /dev/null @@ -1,29 +0,0 @@ -const NEW_LINE = '\n'.charCodeAt(0); - -export async function readAICourseLessonStream( - reader: ReadableStreamDefaultReader, - { - onStream, - onStreamEnd, - }: { - onStream?: (lesson: string) => void; - onStreamEnd?: (lesson: string) => void; - }, -) { - const decoder = new TextDecoder('utf-8'); - let result = ''; - - while (true) { - const { value, done } = await reader.read(); - if (done) { - break; - } - - result += decoder.decode(value); - onStream?.(result); - } - - onStream?.(result); - onStreamEnd?.(result); - reader.releaseLock(); -} diff --git a/src/queries/billing.ts b/src/queries/billing.ts index 717b8a36c..6425b4048 100644 --- a/src/queries/billing.ts +++ b/src/queries/billing.ts @@ -55,7 +55,7 @@ export function billingDetailsOptions() { } export function useIsPaidUser() { - const { data } = useQuery( + const { data, isLoading } = useQuery( { queryKey: ['billing-details'], queryFn: async () => { @@ -67,7 +67,10 @@ export function useIsPaidUser() { queryClient, ); - return data ?? false; + return { + isPaidUser: data ?? false, + isLoading, + }; } type CoursePriceParams = {