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

View File

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

View File

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

View File

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

View File

@ -141,7 +141,7 @@ const DocumentDetails = ({ processedEvent, topics, title, summary, image, price,
return (
<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">
<i className="pi pi-lock text-[60px] text-red-500"></i>
</div>