Limits and errors

feat/ai-courses
Kamran Ahmed 1 month ago
parent 3f4a5bf4bd
commit 5c99f318e8
  1. 30
      src/components/GenerateCourse/AICourseContent.tsx
  2. 49
      src/components/GenerateCourse/AICourseLimit.tsx
  3. 29
      src/components/GenerateCourse/AICourseModuleView.tsx
  4. 12
      src/components/GenerateCourse/AILimitsPopup.tsx
  5. 14
      src/queries/ai-course.ts

@ -11,6 +11,8 @@ import { ErrorIcon } from '../ReactIcons/ErrorIcon';
import { AICourseLimit } from './AICourseLimit';
import { AICourseModuleList } from './AICourseModuleList';
import { AICourseModuleView } from './AICourseModuleView';
import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
import { AILimitsPopup } from './AILimitsPopup';
type AICourseContentProps = {
courseSlug?: string;
@ -22,6 +24,9 @@ type AICourseContentProps = {
export function AICourseContent(props: AICourseContentProps) {
const { course, courseSlug, isLoading, error } = props;
const [showUpgradeModal, setShowUpgradeModal] = useState(false);
const [showAILimitsPopup, setShowAILimitsPopup] = useState(false);
const [activeModuleIndex, setActiveModuleIndex] = useState(0);
const [activeLessonIndex, setActiveLessonIndex] = useState(0);
const [sidebarOpen, setSidebarOpen] = useState(false);
@ -114,6 +119,20 @@ export function AICourseContent(props: AICourseContentProps) {
return (
<section className="flex h-screen flex-grow flex-col overflow-hidden bg-gray-50">
{showUpgradeModal && (
<UpgradeAccountModal onClose={() => setShowUpgradeModal(false)} />
)}
{showAILimitsPopup && (
<AILimitsPopup
onClose={() => setShowAILimitsPopup(false)}
onUpgrade={() => {
setShowAILimitsPopup(false);
setShowUpgradeModal(true);
}}
/>
)}
<div className="border-b border-gray-200 bg-gray-100">
<div className="flex items-center justify-between px-4 py-2">
<a
@ -126,7 +145,10 @@ export function AICourseContent(props: AICourseContentProps) {
</a>
<div className="flex items-center gap-2">
<div className="flex flex-row lg:hidden">
<AICourseLimit />
<AICourseLimit
onUpgrade={() => setShowUpgradeModal(true)}
onShowLimits={() => setShowAILimitsPopup(true)}
/>
</div>
<button
@ -179,7 +201,10 @@ export function AICourseContent(props: AICourseContentProps) {
</div>
<div className="flex gap-2">
<div className="hidden gap-2 lg:flex">
<AICourseLimit />
<AICourseLimit
onUpgrade={() => setShowUpgradeModal(true)}
onShowLimits={() => setShowAILimitsPopup(true)}
/>
</div>
{viewMode === 'module' && (
@ -274,6 +299,7 @@ export function AICourseContent(props: AICourseContentProps) {
onGoToPrevLesson={goToPrevLesson}
onGoToNextLesson={goToNextLesson}
key={`${courseSlug}-${activeModuleIndex}-${activeLessonIndex}`}
onUpgrade={() => setShowUpgradeModal(true)}
/>
)}

@ -1,16 +1,17 @@
import { useQuery } from '@tanstack/react-query';
import { useState } from 'react';
import { getAiCourseLimitOptions } from '../../queries/ai-course';
import { queryClient } from '../../stores/query-client';
import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
import { billingDetailsOptions } from '../../queries/billing';
import { getPercentage } from '../../helper/number';
import { Gift, Info } from 'lucide-react';
import { AILimitsPopup } from './AILimitsPopup';
export function AICourseLimit() {
const [showUpgradeModal, setShowUpgradeModal] = useState(false);
const [showAILimitsPopup, setShowAILimitsPopup] = useState(false);
type AICourseLimitProps = {
onUpgrade: () => void;
onShowLimits: () => void;
};
export function AICourseLimit(props: AICourseLimitProps) {
const { onUpgrade, onShowLimits } = props;
const { data: limits, isLoading } = useQuery(
getAiCourseLimitOptions(),
@ -38,28 +39,16 @@ export function AICourseLimit() {
<>
<button
className="mr-1 flex items-center gap-1 text-sm font-medium underline underline-offset-2 lg:hidden"
onClick={() => setShowAILimitsPopup(true)}
onClick={() => onShowLimits()}
>
<Info className="size-4" />
{totalPercentage}% limit used
</button>
{showAILimitsPopup && (
<AILimitsPopup
used={used}
limit={limit}
onClose={() => setShowAILimitsPopup(false)}
onUpgrade={() => {
setShowAILimitsPopup(false);
setShowUpgradeModal(true);
}}
/>
)}
{(!isPaidUser || isNearLimit) && (
<button
onClick={() => {
setShowAILimitsPopup(true);
onShowLimits();
}}
className="relative hidden h-full min-h-[38px] cursor-pointer items-center overflow-hidden rounded-lg border border-gray-300 px-3 py-1.5 text-sm hover:bg-gray-50 lg:flex"
>
@ -76,19 +65,13 @@ export function AICourseLimit() {
)}
{!isPaidUser && (
<>
<button
className="hidden items-center justify-center gap-1 rounded-md bg-yellow-400 px-4 py-1 text-sm font-medium underline-offset-2 hover:bg-yellow-500 lg:flex"
onClick={() => setShowUpgradeModal(true)}
>
<Gift className="size-4" />
Upgrade
</button>
{showUpgradeModal && (
<UpgradeAccountModal onClose={() => setShowUpgradeModal(false)} />
)}
</>
<button
className="hidden items-center justify-center gap-1 rounded-md bg-yellow-400 px-4 py-1 text-sm font-medium underline-offset-2 hover:bg-yellow-500 lg:flex"
onClick={() => onUpgrade()}
>
<Gift className="size-4" />
Upgrade
</button>
)}
</>
);

@ -38,6 +38,8 @@ type AICourseModuleViewProps = {
onGoToPrevLesson: () => void;
onGoToNextLesson: () => void;
onUpgrade: () => void;
};
export function AICourseModuleView(props: AICourseModuleViewProps) {
@ -53,6 +55,8 @@ export function AICourseModuleView(props: AICourseModuleViewProps) {
onGoToPrevLesson,
onGoToNextLesson,
onUpgrade,
} = props;
const [isLoading, setIsLoading] = useState(true);
@ -262,8 +266,29 @@ export function AICourseModuleView(props: AICourseModuleViewProps) {
)}
{error && isLoggedIn() && (
<div className="mt-8 flex items-center justify-center">
<p className="text-red-500">{error}</p>
<div className="mt-8 flex min-h-[300px] items-center justify-center rounded-xl bg-red-50/80">
{error.includes('reached the limit') ? (
<div className="flex max-w-sm flex-col items-center text-center">
<h2 className="text-xl font-semibold text-red-600">
Limit reached
</h2>
<p className="my-3 text-red-600">
You have reached the AI usage limit for today. Please upgrade
your account to continue.
</p>
<button
onClick={() => {
onUpgrade();
}}
className="rounded-full bg-red-600 px-4 py-1 text-white hover:bg-red-700"
>
Upgrade Account
</button>
</div>
) : (
<p className="text-red-600">{error}</p>
)}
</div>
)}

@ -4,16 +4,22 @@ import { formatCommaNumber } from '../../lib/number';
import { billingDetailsOptions } from '../../queries/billing';
import { queryClient } from '../../stores/query-client';
import { useQuery } from '@tanstack/react-query';
import { getAiCourseLimitOptions } from '../../queries/ai-course';
type AILimitsPopupProps = {
used: number;
limit: number;
onClose: () => void;
onUpgrade: () => void;
};
export function AILimitsPopup(props: AILimitsPopupProps) {
const { used, limit, onClose, onUpgrade } = props;
const { onClose, onUpgrade } = props;
const { data: limits, isLoading } = useQuery(
getAiCourseLimitOptions(),
queryClient,
);
const { used, limit } = limits ?? { used: 0, limit: 0 };
const { data: userBillingDetails, isLoading: isBillingDetailsLoading } =
useQuery(billingDetailsOptions(), queryClient);

@ -15,10 +15,6 @@ type GetAICourseProgressParams = {
aiCourseSlug: string;
};
type GetAICourseProgressBody = {};
type GetAICourseProgressQuery = {};
type GetAICourseProgressResponse = AICourseProgressDocument;
export function getAiCourseProgressOptions(params: GetAICourseProgressParams) {
@ -50,10 +46,6 @@ export interface AICourseDocument {
updatedAt: Date;
}
type GetAICourseBody = {};
type GetAICourseQuery = {};
type GetAICourseResponse = AICourseDocument;
export function getAiCourseOptions(params: GetAICourseParams) {
@ -67,9 +59,7 @@ export function getAiCourseOptions(params: GetAICourseParams) {
};
}
type GetAICourseLimitParams = {};
type GetAICourseLimitResponse = {
export type GetAICourseLimitResponse = {
used: number;
limit: number;
};
@ -84,8 +74,6 @@ export function getAiCourseLimitOptions() {
});
}
type ListUserAiCoursesParams = {};
type ListUserAiCoursesResponse = (AICourseDocument & {
progress: AICourseProgressDocument;
})[];

Loading…
Cancel
Save