@@ -128,8 +141,8 @@ export default function CourseDetails({ processedEvent, paidCourse, decryptedCon
className="w-[344px] h-[194px] object-cover object-top rounded-lg"
/>
- {paidCourse && !decryptedContent && (
-
)}
- {paidCourse && decryptedContent && author && processedEvent?.pubkey !== session?.user?.pubkey && (
+ {paidCourse && decryptionPerformed && author && processedEvent?.pubkey !== session?.user?.pubkey && (
Paid {processedEvent.price} sats
)}
{paidCourse && author && processedEvent?.pubkey === session?.user?.pubkey && (
diff --git a/src/components/content/lists/ContentListItem.js b/src/components/content/lists/ContentListItem.js
index 404c3bb..0f6cd33 100644
--- a/src/components/content/lists/ContentListItem.js
+++ b/src/components/content/lists/ContentListItem.js
@@ -10,14 +10,6 @@ const ContentListItem = (content) => {
const isDraft = Object.keys(content).includes('type');
const isCourse = content && content?.kind === 30004;
- useEffect(() => {
- if (content && content?.kind === 30004) {
- console.log("isDraft", isDraft);
- console.log("content", content);
- console.log("isCourse", isCourse);
- }
- }, [content, isDraft, isCourse]);
-
const handleClick = () => {
let path = '';
diff --git a/src/components/forms/CourseForm.js b/src/components/forms/CourseForm.js
index 991909a..e4c1865 100644
--- a/src/components/forms/CourseForm.js
+++ b/src/components/forms/CourseForm.js
@@ -26,8 +26,8 @@ import 'primeicons/primeicons.css';
const CourseForm = ({ draft = null, isPublished = false }) => {
const [title, setTitle] = useState('');
const [summary, setSummary] = useState('');
- const [checked, setChecked] = useState(false);
- const [price, setPrice] = useState(0);
+ const [isPaidCourse, setIsPaidCourse] = useState(draft?.price ? true : false);
+ const [price, setPrice] = useState(draft?.price || 0);
const [coverImage, setCoverImage] = useState('');
const [lessons, setLessons] = useState([{ id: uuidv4(), title: 'Select a lesson' }]);
const [loadingLessons, setLoadingLessons] = useState(true);
@@ -89,7 +89,7 @@ const CourseForm = ({ draft = null, isPublished = false }) => {
console.log('draft:', draft);
setTitle(draft.title);
setSummary(draft.summary);
- setChecked(draft.price > 0);
+ setIsPaidCourse(draft.price > 0);
setPrice(draft.price || 0);
setCoverImage(draft.image);
// setSelectedLessons(draft.resources || []);
@@ -97,6 +97,10 @@ const CourseForm = ({ draft = null, isPublished = false }) => {
}
}, [draft]);
+ const handlePriceChange = (value) => {
+ setPrice(value);
+ };
+
const handleSubmit = async (e) => {
e.preventDefault();
@@ -198,12 +202,20 @@ const CourseForm = ({ draft = null, isPublished = false }) => {
if (resourcesLoading || !resources || workshopsLoading || !workshops || draftsLoading || !drafts) {
return [];
}
- const draftOptions = drafts.map(draft => ({
+
+ const filterContent = (content) => {
+ console.log('contentttttt', content);
+ // If there is price in content.tags, then it is a paid content 'price' in the 0 index and stringified int in the 1 index
+ const contentPrice = content.tags.find(tag => tag[0] === 'price') ? parseInt(content.tags.find(tag => tag[0] === 'price')[1]) : 0;
+ return isPaidCourse ? contentPrice > 0 : contentPrice === 0;
+ };
+
+ const draftOptions = drafts.filter(filterContent).map(draft => ({
label:
handleLessonSelect(content, index)} selected={lessons[index] && lessons[index].id === draft.id} />,
value: draft.id
}));
- const resourceOptions = resources.map(resource => {
+ const resourceOptions = resources.filter(filterContent).map(resource => {
const { id, kind, pubkey, content, title, summary, image, published_at, d, topics } = parseEvent(resource);
return {
label: handleLessonSelect(content, index)} selected={lessons[index] && lessons[index].id === id} />,
@@ -211,7 +223,7 @@ const CourseForm = ({ draft = null, isPublished = false }) => {
};
});
- const workshopOptions = workshops.map(workshop => {
+ const workshopOptions = workshops.filter(filterContent).map(workshop => {
const { id, kind, pubkey, content, title, summary, image, published_at, d, topics } = parseEvent(workshop);
return {
label: handleLessonSelect(content, index)} selected={lessons[index] && lessons[index].id === id} />,
@@ -251,12 +263,17 @@ const CourseForm = ({ draft = null, isPublished = false }) => {
setCoverImage(e.target.value)} placeholder="Cover Image URL" />
-
+
Paid Course
-
setChecked(e.value)} />
- {checked && (
+ setIsPaidCourse(e.value)} />
+ {isPaidCourse && (
- setPrice(e.value)} placeholder="Price (sats)" />
+ handlePriceChange(e.value)}
+ placeholder="Price (sats)"
+ min={1}
+ />
)}
diff --git a/src/components/forms/EditCourseForm.js b/src/components/forms/EditCourseForm.js
index f0a2437..5956298 100644
--- a/src/components/forms/EditCourseForm.js
+++ b/src/components/forms/EditCourseForm.js
@@ -25,6 +25,7 @@ const EditCourseForm = ({ draft }) => {
const [summary, setSummary] = useState('');
const [checked, setChecked] = useState(false);
const [price, setPrice] = useState(0);
+ const [isPaid, setIsPaid] = useState(false);
const [coverImage, setCoverImage] = useState('');
const [selectedLessons, setSelectedLessons] = useState([]);
const [selectedLessonsLoading, setSelectedLessonsLoading] = useState(false);
@@ -63,11 +64,17 @@ const EditCourseForm = ({ draft }) => {
setSummary(draft.summary);
setChecked(draft.price > 0);
setPrice(draft.price || 0);
+ setIsPaid(draft.price > 0);
setCoverImage(draft.image);
setTopics(draft.topics || ['']);
}
}, [draft, ndk, showToast, parseEvent]);
+ const handlePriceChange = (value) => {
+ setPrice(value);
+ setIsPaid(value > 0);
+ };
+
const handleSubmit = async (e) => {
e.preventDefault();
@@ -90,7 +97,7 @@ const EditCourseForm = ({ draft }) => {
title,
summary,
image: coverImage,
- price: checked ? price : 0,
+ price: isPaid ? price : 0,
topics,
resourceIds: lessonsToUpdate.filter(lesson => lesson && lesson.id).map(lesson => lesson.id)
};
@@ -137,7 +144,12 @@ const EditCourseForm = ({ draft }) => {
return [];
}
- const resourceOptions = resources.map(resource => {
+ const filterContent = (content) => {
+ const contentPrice = content.price || 0;
+ return isPaid ? contentPrice > 0 : contentPrice === 0;
+ };
+
+ const resourceOptions = resources.filter(filterContent).map(resource => {
const parsedResource = parseEvent(resource);
return {
label:
lesson.id === parsedResource.id)} />,
@@ -145,7 +157,7 @@ const EditCourseForm = ({ draft }) => {
};
});
- const workshopOptions = workshops.map(workshop => {
+ const workshopOptions = workshops.filter(filterContent).map(workshop => {
const parsedWorkshop = parseEvent(workshop);
return {
label: lesson.id === parsedWorkshop.id)} />,
@@ -179,7 +191,7 @@ const EditCourseForm = ({ draft }) => {
setChecked(e.value)} />
{checked && (
- setPrice(e.value)} placeholder="Price (sats)" />
+ handlePriceChange(e.value)} placeholder="Price (sats)" />
)}
diff --git a/src/pages/course/[slug]/index.js b/src/pages/course/[slug]/index.js
index a26b1fa..da07c12 100644
--- a/src/pages/course/[slug]/index.js
+++ b/src/pages/course/[slug]/index.js
@@ -8,6 +8,7 @@ import { useNDKContext } from "@/context/NDKContext";
import { useToast } from '@/hooks/useToast';
import { useSession } from 'next-auth/react';
import { nip04 } from 'nostr-tools';
+import { ProgressSpinner } from 'primereact/progressspinner';
const MDDisplay = dynamic(
() => import("@uiw/react-markdown-preview"),
@@ -21,12 +22,13 @@ const Course = () => {
const [lessonIds, setLessonIds] = useState([]);
const [lessons, setLessons] = useState([]);
const [paidCourse, setPaidCourse] = useState(false);
- const [decryptedContent, setDecryptedContent] = useState(null);
-
+ const [decryptionPerformed, setDecryptionPerformed] = useState(false);
+ const [loading, setLoading] = useState(true);
const router = useRouter();
const {ndk, addSigner} = useNDKContext();
const { data: session, update } = useSession();
const { showToast } = useToast();
+
const privkey = process.env.NEXT_PUBLIC_APP_PRIV_KEY;
const pubkey = process.env.NEXT_PUBLIC_APP_PUBLIC_KEY;
@@ -112,21 +114,37 @@ const Course = () => {
useEffect(() => {
const decryptContent = async () => {
- if (session?.user && paidCourse) {
- if (session.user?.purchased?.length > 0) {
- const purchasedCourse = session.user.purchased.find(purchase => purchase.resourceId === course.d);
- if (purchasedCourse) {
- const decryptedContent = await nip04.decrypt(privkey, pubkey, course.content);
- setDecryptedContent(decryptedContent);
+ if (session?.user && paidCourse && !decryptionPerformed) {
+ setLoading(true);
+ const canAccess =
+ session.user.purchased?.some(purchase => purchase.courseId === course?.d) ||
+ session.user?.role?.subscribed ||
+ session.user?.pubkey === course?.pubkey;
+
+ if (canAccess && lessons.length > 0) {
+ try {
+ const decryptedLessons = await Promise.all(lessons.map(async (lesson) => {
+ const decryptedContent = await nip04.decrypt(privkey, pubkey, lesson.content);
+ return { ...lesson, content: decryptedContent };
+ }));
+ setLessons(decryptedLessons);
+ setDecryptionPerformed(true);
+ } catch (error) {
+ console.error('Error decrypting lessons:', error);
}
- } else if (session.user?.role && session.user.role.subscribed) {
- const decryptedContent = await nip04.decrypt(privkey, pubkey, course.content);
- setDecryptedContent(decryptedContent);
}
+ setLoading(false);
}
+ setLoading(false);
}
decryptContent();
- }, [session, paidCourse, course]);
+ }, [session, paidCourse, course, lessons, privkey, pubkey, decryptionPerformed]);
+
+ useEffect(() => {
+ if (course && lessons.length > 0 && (!paidCourse || decryptionPerformed)) {
+ setLoading(false);
+ }
+ }, [course, lessons, paidCourse, decryptionPerformed]);
const handlePaymentSuccess = async (response, newCourse) => {
if (response && response?.preimage) {
@@ -142,12 +160,21 @@ const Course = () => {
showToast('error', 'Payment Error', `Failed to purchase course. Please try again. Error: ${error}`);
}
+ if (loading) {
+ return (
+
+ );
+ }
+
return (
<>
@@ -155,9 +182,7 @@ const Course = () => {
))}
- {
- course?.content &&
- }
+ {course?.content && }
>
);
diff --git a/src/pages/draft/[slug]/index.js b/src/pages/draft/[slug]/index.js
index 2bae292..34996f9 100644
--- a/src/pages/draft/[slug]/index.js
+++ b/src/pages/draft/[slug]/index.js
@@ -227,26 +227,11 @@ export default function Draft() {
['image', draft.image],
...draft.topics.map(topic => ['t', topic]),
['published_at', Math.floor(Date.now() / 1000).toString()],
+ ...(draft?.price ? [['price', draft.price.toString()], ['location', `https://plebdevs.com/details/${draft.id}`]] : []),
];
type = 'workshop';
break;
- case 'course':
- event.kind = 30023;
- event.content = draft.content;
- event.created_at = Math.floor(Date.now() / 1000);
- event.pubkey = user.pubkey;
- event.tags = [
- ['d', NewDTag],
- ['title', draft.title],
- ['summary', draft.summary],
- ['image', draft.image],
- ...draft.topics.map(topic => ['t', topic]),
- ['published_at', Math.floor(Date.now() / 1000).toString()],
- ];
-
- type = 'course';
- break;
default:
return null;
}