Add checks for user logged in, paid course and or subscriber for user being able to mark lesson as completed

This commit is contained in:
austinkelsay 2025-03-31 10:22:25 -05:00
parent 79b8cf1ff8
commit 5c6bf72a99
No known key found for this signature in database
GPG Key ID: 5A763922E5BA08EE
5 changed files with 185 additions and 123 deletions

View File

@ -14,6 +14,7 @@ import useTrackVideoLesson from '@/hooks/tracking/useTrackVideoLesson';
import { Menu } from "primereact/menu"; import { Menu } from "primereact/menu";
import { Toast } from "primereact/toast"; import { Toast } from "primereact/toast";
import MoreOptionsMenu from "@/components/ui/MoreOptionsMenu"; import MoreOptionsMenu from "@/components/ui/MoreOptionsMenu";
import { useSession } from "next-auth/react";
const MDDisplay = dynamic( const MDDisplay = dynamic(
() => import("@uiw/react-markdown-preview"), () => import("@uiw/react-markdown-preview"),
@ -35,6 +36,7 @@ const CombinedLesson = ({ lesson, course, decryptionPerformed, isPaid, setComple
const windowWidth = useWindowWidth(); const windowWidth = useWindowWidth();
const isMobileView = windowWidth <= 768; const isMobileView = windowWidth <= 768;
const isVideo = lesson?.type === 'video'; const isVideo = lesson?.type === 'video';
const { data: session } = useSession();
const { isCompleted: videoCompleted, isTracking: videoTracking, markLessonAsCompleted } = useTrackVideoLesson({ const { isCompleted: videoCompleted, isTracking: videoTracking, markLessonAsCompleted } = useTrackVideoLesson({
lessonId: lesson?.d, lessonId: lesson?.d,
@ -45,46 +47,60 @@ const CombinedLesson = ({ lesson, course, decryptionPerformed, isPaid, setComple
decryptionPerformed decryptionPerformed
}); });
const menuItems = [ const buildMenuItems = () => {
{ const items = [];
label: 'Mark as completed',
icon: 'pi pi-check-circle', const hasAccess = session?.user && (
command: async () => { !isPaid ||
try { decryptionPerformed ||
await markLessonAsCompleted(); session.user.role?.subscribed
setCompleted(lesson.id); );
toastRef.current.show({
severity: 'success', if (hasAccess) {
summary: 'Success', items.push({
detail: 'Lesson marked as completed', label: 'Mark as completed',
life: 3000 icon: 'pi pi-check-circle',
}); command: async () => {
} catch (error) { try {
console.error('Failed to mark lesson as completed:', error); await markLessonAsCompleted();
toastRef.current.show({ setCompleted(lesson.id);
severity: 'error', toastRef.current.show({
summary: 'Error', severity: 'success',
detail: 'Failed to mark lesson as completed', summary: 'Success',
life: 3000 detail: 'Lesson marked as completed',
}); life: 3000
});
} catch (error) {
console.error('Failed to mark lesson as completed:', error);
toastRef.current.show({
severity: 'error',
summary: 'Error',
detail: 'Failed to mark lesson as completed',
life: 3000
});
}
} }
} });
}, }
{
items.push({
label: 'Open lesson', label: 'Open lesson',
icon: 'pi pi-arrow-up-right', icon: 'pi pi-arrow-up-right',
command: () => { command: () => {
window.open(`/details/${lesson.id}`, '_blank'); window.open(`/details/${lesson.id}`, '_blank');
} }
}, });
{
items.push({
label: 'View Nostr note', label: 'View Nostr note',
icon: 'pi pi-globe', icon: 'pi pi-globe',
command: () => { command: () => {
window.open(`https://habla.news/a/${nAddress}`, '_blank'); window.open(`https://habla.news/a/${nAddress}`, '_blank');
} }
} });
];
return items;
};
useEffect(() => { useEffect(() => {
const handleYouTubeMessage = (event) => { const handleYouTubeMessage = (event) => {
@ -270,7 +286,7 @@ const CombinedLesson = ({ lesson, course, decryptionPerformed, isPaid, setComple
</div> </div>
<div className="flex justify-end"> <div className="flex justify-end">
<MoreOptionsMenu <MoreOptionsMenu
menuItems={menuItems} menuItems={buildMenuItems()}
additionalLinks={lesson?.additionalLinks || []} additionalLinks={lesson?.additionalLinks || []}
isMobileView={isMobileView} isMobileView={isMobileView}
/> />

View File

@ -12,6 +12,7 @@ import useWindowWidth from "@/hooks/useWindowWidth";
import { nip19 } from "nostr-tools"; import { nip19 } from "nostr-tools";
import appConfig from "@/config/appConfig"; import appConfig from "@/config/appConfig";
import MoreOptionsMenu from "@/components/ui/MoreOptionsMenu"; import MoreOptionsMenu from "@/components/ui/MoreOptionsMenu";
import { useSession } from "next-auth/react";
const MDDisplay = dynamic( const MDDisplay = dynamic(
() => import("@uiw/react-markdown-preview"), () => import("@uiw/react-markdown-preview"),
@ -28,6 +29,7 @@ const CourseLesson = ({ lesson, course, decryptionPerformed, isPaid, setComplete
const toastRef = useRef(null); const toastRef = useRef(null);
const windowWidth = useWindowWidth(); const windowWidth = useWindowWidth();
const isMobileView = windowWidth <= 768; const isMobileView = windowWidth <= 768;
const { data: session } = useSession();
const readTime = lesson?.content ? Math.max(30, Math.ceil(lesson.content.length / 20)) : 60; const readTime = lesson?.content ? Math.max(30, Math.ceil(lesson.content.length / 20)) : 60;
@ -39,39 +41,51 @@ const CourseLesson = ({ lesson, course, decryptionPerformed, isPaid, setComplete
decryptionPerformed decryptionPerformed
}); });
const menuItems = [ const buildMenuItems = () => {
{ const items = [];
label: 'Mark as completed',
icon: 'pi pi-check-circle', const hasAccess = session?.user && (
command: async () => { !isPaid ||
try { decryptionPerformed ||
await markLessonAsCompleted(); session.user.role?.subscribed
setCompleted && setCompleted(lesson.id); );
toastRef.current.show({
severity: 'success', if (hasAccess) {
summary: 'Success', items.push({
detail: 'Lesson marked as completed', label: 'Mark as completed',
life: 3000 icon: 'pi pi-check-circle',
}); command: async () => {
} catch (error) { try {
console.error('Failed to mark lesson as completed:', error); await markLessonAsCompleted();
toastRef.current.show({ setCompleted && setCompleted(lesson.id);
severity: 'error', toastRef.current.show({
summary: 'Error', severity: 'success',
detail: 'Failed to mark lesson as completed', summary: 'Success',
life: 3000 detail: 'Lesson marked as completed',
}); life: 3000
});
} catch (error) {
console.error('Failed to mark lesson as completed:', error);
toastRef.current.show({
severity: 'error',
summary: 'Error',
detail: 'Failed to mark lesson as completed',
life: 3000
});
}
} }
} });
}, }
{
items.push({
label: 'Open lesson', label: 'Open lesson',
icon: 'pi pi-arrow-up-right', icon: 'pi pi-arrow-up-right',
command: () => { command: () => {
window.open(`/details/${lesson.id}`, '_blank'); window.open(`/details/${lesson.id}`, '_blank');
} }
}, });
{
items.push({
label: 'View Nostr note', label: 'View Nostr note',
icon: 'pi pi-globe', icon: 'pi pi-globe',
command: () => { command: () => {
@ -85,10 +99,10 @@ const CourseLesson = ({ lesson, course, decryptionPerformed, isPaid, setComplete
window.open(`https://habla.news/a/${addr}`, '_blank'); window.open(`https://habla.news/a/${addr}`, '_blank');
} }
} }
} });
];
return items;
// Add additional links to menu items if they exist };
useEffect(() => { useEffect(() => {
if (!zaps || zapsLoading || zapsError) return; if (!zaps || zapsLoading || zapsError) return;
@ -164,7 +178,7 @@ const CourseLesson = ({ lesson, course, decryptionPerformed, isPaid, setComplete
</div> </div>
<div className="flex justify-end"> <div className="flex justify-end">
<MoreOptionsMenu <MoreOptionsMenu
menuItems={menuItems} menuItems={buildMenuItems()}
additionalLinks={lesson?.additionalLinks || []} additionalLinks={lesson?.additionalLinks || []}
isMobileView={isMobileView} isMobileView={isMobileView}
/> />

View File

@ -13,6 +13,7 @@ import appConfig from "@/config/appConfig";
import useTrackDocumentLesson from "@/hooks/tracking/useTrackDocumentLesson"; import useTrackDocumentLesson from "@/hooks/tracking/useTrackDocumentLesson";
import { Toast } from "primereact/toast"; import { Toast } from "primereact/toast";
import MoreOptionsMenu from "@/components/ui/MoreOptionsMenu"; import MoreOptionsMenu from "@/components/ui/MoreOptionsMenu";
import { useSession } from "next-auth/react";
const MDDisplay = dynamic( const MDDisplay = dynamic(
() => import("@uiw/react-markdown-preview"), () => import("@uiw/react-markdown-preview"),
@ -32,6 +33,7 @@ const DocumentLesson = ({ lesson, course, decryptionPerformed, isPaid, setComple
const toastRef = useRef(null); const toastRef = useRef(null);
// todo implement real read time needs to be on form // todo implement real read time needs to be on form
const readTime = 120; const readTime = 120;
const { data: session } = useSession();
const { isCompleted, isTracking, markLessonAsCompleted } = useTrackDocumentLesson({ const { isCompleted, isTracking, markLessonAsCompleted } = useTrackDocumentLesson({
lessonId: lesson?.d, lessonId: lesson?.d,
@ -41,46 +43,60 @@ const DocumentLesson = ({ lesson, course, decryptionPerformed, isPaid, setComple
decryptionPerformed: decryptionPerformed, decryptionPerformed: decryptionPerformed,
}); });
const menuItems = [ const buildMenuItems = () => {
{ const items = [];
label: 'Mark as completed',
icon: 'pi pi-check-circle', const hasAccess = session?.user && (
command: async () => { !isPaid ||
try { decryptionPerformed ||
await markLessonAsCompleted(); session.user.role?.subscribed
setCompleted && setCompleted(lesson.id); );
toastRef.current.show({
severity: 'success', if (hasAccess) {
summary: 'Success', items.push({
detail: 'Lesson marked as completed', label: 'Mark as completed',
life: 3000 icon: 'pi pi-check-circle',
}); command: async () => {
} catch (error) { try {
console.error('Failed to mark lesson as completed:', error); await markLessonAsCompleted();
toastRef.current.show({ setCompleted && setCompleted(lesson.id);
severity: 'error', toastRef.current.show({
summary: 'Error', severity: 'success',
detail: 'Failed to mark lesson as completed', summary: 'Success',
life: 3000 detail: 'Lesson marked as completed',
}); life: 3000
});
} catch (error) {
console.error('Failed to mark lesson as completed:', error);
toastRef.current.show({
severity: 'error',
summary: 'Error',
detail: 'Failed to mark lesson as completed',
life: 3000
});
}
} }
} });
}, }
{
items.push({
label: 'Open lesson', label: 'Open lesson',
icon: 'pi pi-arrow-up-right', icon: 'pi pi-arrow-up-right',
command: () => { command: () => {
window.open(`/details/${lesson.id}`, '_blank'); window.open(`/details/${lesson.id}`, '_blank');
} }
}, });
{
items.push({
label: 'View Nostr note', label: 'View Nostr note',
icon: 'pi pi-globe', icon: 'pi pi-globe',
command: () => { command: () => {
window.open(`https://habla.news/a/${nAddress}`, '_blank'); window.open(`https://habla.news/a/${nAddress}`, '_blank');
} }
} });
];
return items;
};
useEffect(() => { useEffect(() => {
if (!zaps || zapsLoading || zapsError) return; if (!zaps || zapsLoading || zapsError) return;
@ -183,7 +199,7 @@ const DocumentLesson = ({ lesson, course, decryptionPerformed, isPaid, setComple
</div> </div>
<div className="flex justify-end"> <div className="flex justify-end">
<MoreOptionsMenu <MoreOptionsMenu
menuItems={menuItems} menuItems={buildMenuItems()}
additionalLinks={lesson?.additionalLinks || []} additionalLinks={lesson?.additionalLinks || []}
isMobileView={isMobileView} isMobileView={isMobileView}
/> />

View File

@ -13,6 +13,7 @@ import useWindowWidth from "@/hooks/useWindowWidth";
import useTrackVideoLesson from '@/hooks/tracking/useTrackVideoLesson'; import useTrackVideoLesson from '@/hooks/tracking/useTrackVideoLesson';
import { Toast } from "primereact/toast"; import { Toast } from "primereact/toast";
import MoreOptionsMenu from "@/components/ui/MoreOptionsMenu"; import MoreOptionsMenu from "@/components/ui/MoreOptionsMenu";
import { useSession } from "next-auth/react";
const MDDisplay = dynamic( const MDDisplay = dynamic(
() => import("@uiw/react-markdown-preview"), () => import("@uiw/react-markdown-preview"),
@ -33,6 +34,7 @@ const VideoLesson = ({ lesson, course, decryptionPerformed, isPaid, setCompleted
const mdDisplayRef = useRef(null); const mdDisplayRef = useRef(null);
const menuRef = useRef(null); const menuRef = useRef(null);
const toastRef = useRef(null); const toastRef = useRef(null);
const { data: session } = useSession();
const { isCompleted, isTracking, markLessonAsCompleted } = useTrackVideoLesson({ const { isCompleted, isTracking, markLessonAsCompleted } = useTrackVideoLesson({
lessonId: lesson?.d, lessonId: lesson?.d,
@ -43,46 +45,60 @@ const VideoLesson = ({ lesson, course, decryptionPerformed, isPaid, setCompleted
decryptionPerformed decryptionPerformed
}); });
const menuItems = [ const buildMenuItems = () => {
{ const items = [];
label: 'Mark as completed',
icon: 'pi pi-check-circle', const hasAccess = session?.user && (
command: async () => { !isPaid ||
try { decryptionPerformed ||
await markLessonAsCompleted(); session.user.role?.subscribed
setCompleted(lesson.id); );
toastRef.current.show({
severity: 'success', if (hasAccess) {
summary: 'Success', items.push({
detail: 'Lesson marked as completed', label: 'Mark as completed',
life: 3000 icon: 'pi pi-check-circle',
}); command: async () => {
} catch (error) { try {
console.error('Failed to mark lesson as completed:', error); await markLessonAsCompleted();
toastRef.current.show({ setCompleted(lesson.id);
severity: 'error', toastRef.current.show({
summary: 'Error', severity: 'success',
detail: 'Failed to mark lesson as completed', summary: 'Success',
life: 3000 detail: 'Lesson marked as completed',
}); life: 3000
});
} catch (error) {
console.error('Failed to mark lesson as completed:', error);
toastRef.current.show({
severity: 'error',
summary: 'Error',
detail: 'Failed to mark lesson as completed',
life: 3000
});
}
} }
} });
}, }
{
items.push({
label: 'Open lesson', label: 'Open lesson',
icon: 'pi pi-arrow-up-right', icon: 'pi pi-arrow-up-right',
command: () => { command: () => {
window.open(`/details/${lesson.id}`, '_blank'); window.open(`/details/${lesson.id}`, '_blank');
} }
}, });
{
items.push({
label: 'View Nostr note', label: 'View Nostr note',
icon: 'pi pi-globe', icon: 'pi pi-globe',
command: () => { command: () => {
window.open(`https://habla.news/a/${nAddress}`, '_blank'); window.open(`https://habla.news/a/${nAddress}`, '_blank');
} }
} });
];
return items;
};
useEffect(() => { useEffect(() => {
const handleYouTubeMessage = (event) => { const handleYouTubeMessage = (event) => {
@ -239,7 +255,7 @@ const VideoLesson = ({ lesson, course, decryptionPerformed, isPaid, setCompleted
</div> </div>
<div className="flex justify-end"> <div className="flex justify-end">
<MoreOptionsMenu <MoreOptionsMenu
menuItems={menuItems} menuItems={buildMenuItems()}
additionalLinks={lesson?.additionalLinks || []} additionalLinks={lesson?.additionalLinks || []}
isMobileView={isMobileView} isMobileView={isMobileView}
/> />

View File

@ -141,7 +141,7 @@ const DocumentDetails = ({ processedEvent, topics, title, summary, image, price,
return ( return (
<div className="w-full px-4"> <div className="w-full px-4">
<div className="w-full p-8 rounded-lg flex flex-col items-center justify-center bg-gray-800"> <div className="w-full p-8 rounded-lg flex flex-col items-center justify-center">
<div className="mx-auto py-auto"> <div className="mx-auto py-auto">
<i className="pi pi-lock text-[60px] text-red-500"></i> <i className="pi pi-lock text-[60px] text-red-500"></i>
</div> </div>