Changed resource to document on the frontend

This commit is contained in:
austinkelsay 2024-09-15 15:15:58 -05:00
parent aa13faaf44
commit cb3f124c3a
31 changed files with 203 additions and 203 deletions

View File

@ -15,7 +15,7 @@ const BottomBar = () => {
<i className="pi pi-home text-2xl" /> <i className="pi pi-home text-2xl" />
</div> </div>
<div onClick={() => router.push('/content?tag=all')} className={`hover:bg-gray-700 cursor-pointer px-4 py-3 rounded-lg ${isActive('/content') ? 'bg-gray-700' : ''}`}> <div onClick={() => router.push('/content?tag=all')} className={`hover:bg-gray-700 cursor-pointer px-4 py-3 rounded-lg ${isActive('/content') ? 'bg-gray-700' : ''}`}>
<i className="pi pi-video text-2xl" /> <i className="pi pi-play-circle text-2xl" />
</div> </div>
<div onClick={() => router.push('/feed?channel=global')} className={`hover:bg-gray-700 cursor-pointer px-4 py-3 rounded-lg ${isActive('/feed') ? 'bg-gray-700' : ''}`}> <div onClick={() => router.push('/feed?channel=global')} className={`hover:bg-gray-700 cursor-pointer px-4 py-3 rounded-lg ${isActive('/feed') ? 'bg-gray-700' : ''}`}>
<i className="pi pi-comments text-2xl" /> <i className="pi pi-comments text-2xl" />

View File

@ -4,6 +4,7 @@ import { useImageProxy } from "@/hooks/useImageProxy";
import { formatUnixTimestamp } from "@/utils/time"; import { formatUnixTimestamp } from "@/utils/time";
import GenericButton from "@/components/buttons/GenericButton"; import GenericButton from "@/components/buttons/GenericButton";
const SelectedContentItem = ({ content, onRemove }) => { const SelectedContentItem = ({ content, onRemove }) => {
console.log('content:', content);
const { returnImageProxy } = useImageProxy(); const { returnImageProxy } = useImageProxy();
return ( return (

View File

@ -1,10 +1,9 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Carousel } from 'primereact/carousel'; import { Carousel } from 'primereact/carousel';
import { parseEvent } from '@/utils/nostr'; import { parseEvent } from '@/utils/nostr';
// import ResourceTemplate from '@/components/content/carousels/templates/ResourceTemplate';
import { DocumentTemplate } from '@/components/content/carousels/templates/DocumentTemplate'; import { DocumentTemplate } from '@/components/content/carousels/templates/DocumentTemplate';
import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton'; import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton';
import { useResources } from '@/hooks/nostr/useResources'; import { useDocuments } from '@/hooks/nostr/useDocuments';
const responsiveOptions = [ const responsiveOptions = [
{ {
@ -24,44 +23,42 @@ const responsiveOptions = [
} }
]; ];
export default function ResourcesCarousel() { export default function DocumentsCarousel() {
const [processedResources, setProcessedResources] = useState([]); const [processedDocuments, setProcessedDocuments] = useState([]);
const { resources, resourcesLoading, resourcesError } = useResources() const { documents, documentsLoading, documentsError } = useDocuments()
useEffect(() => { useEffect(() => {
const fetch = async () => { const fetch = async () => {
try { try {
if (resources && resources.length > 0) { if (documents && documents.length > 0) {
const processedResources = resources.map(resource => parseEvent(resource)); const processedDocuments = documents.map(document => parseEvent(document));
// Sort resources by created_at in descending order (most recent first) // Sort documents by created_at in descending order (most recent first)
const sortedResources = processedResources.sort((a, b) => b.created_at - a.created_at); const sortedDocuments = processedDocuments.sort((a, b) => b.created_at - a.created_at);
console.log("Sorted resources:", sortedResources); setProcessedDocuments(sortedDocuments);
setProcessedResources(sortedResources);
} else { } else {
console.log('No resources fetched or empty array returned'); console.log('No documents fetched or empty array returned');
} }
} catch (error) { } catch (error) {
console.error('Error fetching resources:', error); console.error('Error fetching documents:', error);
} }
}; };
fetch(); fetch();
}, [resources]); }, [documents]);
if (resourcesError) { if (documentsError) {
return <div>Error: {resourcesError.message}</div> return <div>Error: {documentsError.message}</div>
} }
return ( return (
<> <>
<h3 className="ml-[6%] mt-4">Resources</h3> <h3 className="ml-[6%] mt-4">Documents</h3>
<Carousel <Carousel
value={resourcesLoading || !processedResources.length ? [{}, {}, {}] : [...processedResources]} value={documentsLoading || !processedDocuments.length ? [{}, {}, {}] : [...processedDocuments]}
numVisible={2} numVisible={2}
itemTemplate={(item) => itemTemplate={(item) =>
processedResources.length > 0 ? processedDocuments.length > 0 ?
<DocumentTemplate key={item.id} document={item} /> : <DocumentTemplate key={item.id} document={item} /> :
<TemplateSkeleton key={Math.random()} /> <TemplateSkeleton key={Math.random()} />
} }

View File

@ -64,7 +64,7 @@ export default function GenericCarousel({items, selectedTopic, title}) {
value={carouselItems} value={carouselItems}
itemTemplate={(item) => { itemTemplate={(item) => {
if (carouselItems.length > 0) { if (carouselItems.length > 0) {
if (item.type === 'resource') { if (item.type === 'document') {
return <DocumentTemplate key={item.id} document={item} />; return <DocumentTemplate key={item.id} document={item} />;
} else if (item.type === 'video') { } else if (item.type === 'video') {
return <VideoTemplate key={item.id} video={item} />; return <VideoTemplate key={item.id} video={item} />;

View File

@ -33,9 +33,9 @@ const promotions = [
}, },
{ {
id: 4, id: 4,
category: "RESOURCES", category: "DOCUMENTS",
title: "In-depth Resources and Documentation", title: "In-depth Resources and Documentation",
description: "Access our extensive library of resources, including guides, documentation, and best practices for Bitcoin development.", description: "Access our extensive library of documents, including guides, resources, and best practices for Bitcoin development.",
icon: "pi pi-file", icon: "pi pi-file",
image: "https://img.freepik.com/free-photo/programming-background-with-person-working-with-codes-computer_23-2150010125.jpg", image: "https://img.freepik.com/free-photo/programming-background-with-person-working-with-codes-computer_23-2150010125.jpg",
}, },
@ -101,9 +101,9 @@ const InteractivePromotionalCarousel = () => {
return ( return (
<GenericButton onClick={() => router.push('/content?tag=videos')} icon={<i className="pi pi-video pr-2" />} label="View All Videos" className="w-fit py-2 font-semibold" size="small" outlined /> <GenericButton onClick={() => router.push('/content?tag=videos')} icon={<i className="pi pi-video pr-2" />} label="View All Videos" className="w-fit py-2 font-semibold" size="small" outlined />
); );
case "RESOURCES": case "DOCUMENTS":
return ( return (
<GenericButton onClick={() => router.push('/content?tag=resources')} icon={<i className="pi pi-file pr-2 pb-1" />} label="View All Resources" className="w-fit py-2 font-semibold" size="small" outlined /> <GenericButton onClick={() => router.push('/content?tag=documents')} icon={<i className="pi pi-file pr-2 pb-1" />} label="View All Documents" className="w-fit py-2 font-semibold" size="small" outlined />
); );
case "COMMUNITY": case "COMMUNITY":
return ( return (
@ -149,9 +149,9 @@ const InteractivePromotionalCarousel = () => {
return ( return (
<GenericButton onClick={() => router.push('/content?tag=videos')} icon={<i className="pi pi-video pr-2" />} label="View All Videos" className="py-2 font-semibold" size="small" outlined /> <GenericButton onClick={() => router.push('/content?tag=videos')} icon={<i className="pi pi-video pr-2" />} label="View All Videos" className="py-2 font-semibold" size="small" outlined />
); );
case "RESOURCES": case "DOCUMENTS":
return ( return (
<GenericButton onClick={() => router.push('/content?tag=resources')} icon={<i className="pi pi-file pr-2 pb-1" />} label="View All Resources" className="py-2 font-semibold" size="small" outlined /> <GenericButton onClick={() => router.push('/content?tag=documents')} icon={<i className="pi pi-file pr-2 pb-1" />} label="View All Documents" className="py-2 font-semibold" size="small" outlined />
); );
case "COMMUNITY": case "COMMUNITY":
return ( return (

View File

@ -94,14 +94,14 @@ export function CourseTemplate({ course }) {
{course.description || course.summary && ( {course.description || course.summary && (
<> <>
{course.description && ( {course.description && (
<div className="text-xl mt-4"> <div>
{course.description.split('\n').map((line, index) => ( {course.description.split('\n').map((line, index) => (
<p key={index}>{line}</p> <p key={index}>{line}</p>
))} ))}
</div> </div>
)} )}
{course.summary && ( {course.summary && (
<div className="text-xl mt-4"> <div>
{course.summary.split('\n').map((line, index) => ( {course.summary.split('\n').map((line, index) => (
<p key={index}>{line}</p> <p key={index}>{line}</p>
))} ))}

View File

@ -82,14 +82,14 @@ export function DocumentTemplate({ document }) {
{document.description || document.summary && ( {document.description || document.summary && (
<> <>
{document.description && ( {document.description && (
<div className="text-xl mt-4"> <div>
{document.description.split('\n').map((line, index) => ( {document.description.split('\n').map((line, index) => (
<p key={index}>{line}</p> <p key={index}>{line}</p>
))} ))}
</div> </div>
)} )}
{document.summary && ( {document.summary && (
<div className="text-xl mt-4"> <div>
{document.summary.split('\n').map((line, index) => ( {document.summary.split('\n').map((line, index) => (
<p key={index}>{line}</p> <p key={index}>{line}</p>
))} ))}

View File

@ -83,14 +83,14 @@ export function VideoTemplate({ video }) {
{video.description || video.summary && ( {video.description || video.summary && (
<> <>
{video.description && ( {video.description && (
<div className="text-xl mt-4"> <div>
{video.description.split('\n').map((line, index) => ( {video.description.split('\n').map((line, index) => (
<p key={index}>{line}</p> <p key={index}>{line}</p>
))} ))}
</div> </div>
)} )}
{video.summary && ( {video.summary && (
<div className="text-xl mt-4"> <div>
{video.summary.split('\n').map((line, index) => ( {video.summary.split('\n').map((line, index) => (
<p key={index}>{line}</p> <p key={index}>{line}</p>
))} ))}

View File

@ -95,7 +95,7 @@ const CourseLesson = ({ lesson, course, decryptionPerformed, isPaid }) => {
{lesson && ( {lesson && (
<div className='flex flex-col items-center justify-between rounded-lg h-72 p-4 bg-gray-700 drop-shadow-md'> <div className='flex flex-col items-center justify-between rounded-lg h-72 p-4 bg-gray-700 drop-shadow-md'>
<Image <Image
alt="resource thumbnail" alt="course thumbnail"
src={returnImageProxy(lesson.image)} src={returnImageProxy(lesson.image)}
width={344} width={344}
height={194} height={194}

View File

@ -130,8 +130,6 @@ const DocumentLesson = ({ lesson, course, decryptionPerformed, isPaid }) => {
</div> </div>
</div> </div>
<Divider /> <Divider />
</div>
{renderContent()}
{lesson?.additionalLinks && lesson.additionalLinks.length > 0 && ( {lesson?.additionalLinks && lesson.additionalLinks.length > 0 && (
<div className='mt-6 bg-gray-800/90 rounded-lg p-4'> <div className='mt-6 bg-gray-800/90 rounded-lg p-4'>
<h3 className='text-lg font-semibold mb-2 text-white'>External links:</h3> <h3 className='text-lg font-semibold mb-2 text-white'>External links:</h3>
@ -147,6 +145,8 @@ const DocumentLesson = ({ lesson, course, decryptionPerformed, isPaid }) => {
</div> </div>
)} )}
</div> </div>
{renderContent()}
</div>
) )
} }

View File

@ -212,7 +212,7 @@ export default function DraftCourseDetails({ processedEvent, draftId, lessons })
// Step 6: Show success message and redirect // Step 6: Show success message and redirect
showToast('success', 'Success', 'Course created successfully'); showToast('success', 'Success', 'Course created successfully');
router.push(`/course/${courseEvent.id}`); router.push("/");
} catch (error) { } catch (error) {
console.error('Error creating course:', error); console.error('Error creating course:', error);
@ -252,7 +252,7 @@ export default function DraftCourseDetails({ processedEvent, draftId, lessons })
console.log('Draft:', draft); console.log('Draft:', draft);
switch (draft?.type) { switch (draft?.type) {
case 'resource': case 'document':
if (draft?.price) { if (draft?.price) {
// encrypt the content with NEXT_PUBLIC_APP_PRIV_KEY to NEXT_PUBLIC_APP_PUBLIC_KEY // encrypt the content with NEXT_PUBLIC_APP_PRIV_KEY to NEXT_PUBLIC_APP_PUBLIC_KEY
encryptedContent = await nip04.encrypt(process.env.NEXT_PUBLIC_APP_PRIV_KEY, process.env.NEXT_PUBLIC_APP_PUBLIC_KEY, draft.content); encryptedContent = await nip04.encrypt(process.env.NEXT_PUBLIC_APP_PRIV_KEY, process.env.NEXT_PUBLIC_APP_PUBLIC_KEY, draft.content);
@ -273,7 +273,7 @@ export default function DraftCourseDetails({ processedEvent, draftId, lessons })
...(draft?.additionalLinks ? draft.additionalLinks.map(link => ['r', link]) : []), ...(draft?.additionalLinks ? draft.additionalLinks.map(link => ['r', link]) : []),
]; ];
type = 'resource'; type = 'document';
break; break;
case 'video': case 'video':
if (draft?.price) { if (draft?.price) {

View File

@ -110,7 +110,7 @@ const DraftCourseLesson = ({ lesson, course }) => {
{lesson && ( {lesson && (
<div className='flex flex-col items-center justify-between rounded-lg h-72 p-4 bg-gray-700 drop-shadow-md'> <div className='flex flex-col items-center justify-between rounded-lg h-72 p-4 bg-gray-700 drop-shadow-md'>
<Image <Image
alt="resource thumbnail" alt="course thumbnail"
src={returnImageProxy(lesson.image)} src={returnImageProxy(lesson.image)}
width={344} width={344}
height={194} height={194}

View File

@ -22,7 +22,7 @@ import { Tooltip } from 'primereact/tooltip';
import 'primereact/resources/primereact.min.css'; import 'primereact/resources/primereact.min.css';
// todo make the summarry save in a formatted way so we can keep this spaces and line breaks // todo make the summarry save in a formatted way so we can keep this spaces and line breaks
const ResourceForm = ({ draft = null, isPublished = false }) => { const DocumentForm = ({ draft = null, isPublished = false }) => {
const [title, setTitle] = useState(draft?.title || ''); const [title, setTitle] = useState(draft?.title || '');
const [summary, setSummary] = useState(draft?.summary || ''); const [summary, setSummary] = useState(draft?.summary || '');
const [isPaidResource, setIsPaidResource] = useState(draft?.price ? true : false); const [isPaidResource, setIsPaidResource] = useState(draft?.price ? true : false);
@ -104,7 +104,7 @@ const ResourceForm = ({ draft = null, isPublished = false }) => {
content, content,
d: draft.d, d: draft.d,
image: coverImage, image: coverImage,
topics: [...new Set([...topics.map(topic => topic.trim().toLowerCase()), 'resource'])], topics: [...new Set([...topics.map(topic => topic.trim().toLowerCase()), 'document'])],
additionalLinks: additionalLinks.filter(link => link.trim() !== '') additionalLinks: additionalLinks.filter(link => link.trim() !== '')
} }
@ -127,14 +127,14 @@ const ResourceForm = ({ draft = null, isPublished = false }) => {
// update the resource with new noteId // update the resource with new noteId
const response = await axios.put(`/api/resources/${draft.d}`, { noteId: event.id }); const response = await axios.put(`/api/resources/${draft.d}`, { noteId: event.id });
console.log('response', response); console.log('response', response);
showToast('success', 'Success', 'Resource published successfully.'); showToast('success', 'Success', 'Document published successfully.');
router.push(`/details/${event.id}`); router.push(`/details/${event.id}`);
} else { } else {
showToast('error', 'Error', 'Failed to publish resource. Please try again.'); showToast('error', 'Error', 'Failed to publish document. Please try again.');
} }
} catch (error) { } catch (error) {
console.error(error); console.error(error);
showToast('error', 'Error', 'Failed to publish resource. Please try again.'); showToast('error', 'Error', 'Failed to publish document. Please try again.');
} }
} }
@ -151,11 +151,11 @@ const ResourceForm = ({ draft = null, isPublished = false }) => {
const payload = { const payload = {
title, title,
summary, summary,
type: 'resource', type: 'document',
price: isPaidResource ? price : null, price: isPaidResource ? price : null,
content, content,
image: coverImage, image: coverImage,
topics: [...new Set([...topics.map(topic => topic.trim().toLowerCase()), 'resource'])], topics: [...new Set([...topics.map(topic => topic.trim().toLowerCase()), 'document'])],
additionalLinks: additionalLinks.filter(link => link.trim() !== '') additionalLinks: additionalLinks.filter(link => link.trim() !== '')
}; };
@ -171,7 +171,7 @@ const ResourceForm = ({ draft = null, isPublished = false }) => {
axios[method](url, payload) axios[method](url, payload)
.then(response => { .then(response => {
if (response.status === 200 || response.status === 201) { if (response.status === 200 || response.status === 201) {
showToast('success', 'Success', draft ? 'Resource updated successfully.' : 'Resource saved as draft.'); showToast('success', 'Success', draft ? 'Document updated successfully.' : 'Document saved as draft.');
if (response.data?.id) { if (response.data?.id) {
router.push(`/draft/${response.data.id}`); router.push(`/draft/${response.data.id}`);
@ -180,7 +180,7 @@ const ResourceForm = ({ draft = null, isPublished = false }) => {
}) })
.catch(error => { .catch(error => {
console.error(error); console.error(error);
showToast('error', 'Error', 'Failed to save resource. Please try again.'); showToast('error', 'Error', 'Failed to save document. Please try again.');
}); });
} }
}; };
@ -230,7 +230,7 @@ const ResourceForm = ({ draft = null, isPublished = false }) => {
</div> </div>
<div className="p-inputgroup flex-1 mt-8 flex-col"> <div className="p-inputgroup flex-1 mt-8 flex-col">
<p className="py-2">Paid Resource</p> <p className="py-2">Paid Document</p>
<InputSwitch autoResize checked={isPaidResource} onChange={(e) => setIsPaidResource(e.value)} /> <InputSwitch autoResize checked={isPaidResource} onChange={(e) => setIsPaidResource(e.value)} />
{isPaidResource && ( {isPaidResource && (
<div className="p-inputgroup flex-1 py-4"> <div className="p-inputgroup flex-1 py-4">
@ -292,4 +292,4 @@ const ResourceForm = ({ draft = null, isPublished = false }) => {
); );
} }
export default ResourceForm; export default DocumentForm;

View File

@ -9,7 +9,7 @@ import { useRouter } from 'next/router';
import { useToast } from '@/hooks/useToast'; import { useToast } from '@/hooks/useToast';
import { parseEvent } from '@/utils/nostr'; import { parseEvent } from '@/utils/nostr';
import { useDraftsQuery } from '@/hooks/apiQueries/useDraftsQuery'; import { useDraftsQuery } from '@/hooks/apiQueries/useDraftsQuery';
import { useResources } from '@/hooks/nostr/useResources'; import { useDocuments } from '@/hooks/nostr/useDocuments';
import { useVideos } from '@/hooks/nostr/useVideos'; import { useVideos } from '@/hooks/nostr/useVideos';
import axios from 'axios'; import axios from 'axios';
import LessonSelector from './LessonSelector'; import LessonSelector from './LessonSelector';
@ -27,16 +27,17 @@ const CourseForm = ({ draft = null }) => {
const { data: session } = useSession(); const { data: session } = useSession();
const router = useRouter(); const router = useRouter();
const { showToast } = useToast(); const { showToast } = useToast();
const { resources, resourcesLoading, resourcesError } = useResources(); const { documents, documentsLoading, documentsError } = useDocuments();
const { videos, videosLoading, videosError } = useVideos(); const { videos, videosLoading, videosError } = useVideos();
const { drafts, draftsLoading, draftsError } = useDraftsQuery(); const { drafts, draftsLoading, draftsError } = useDraftsQuery();
useEffect(() => { useEffect(() => {
if (draft && resources && videos && drafts) { if (draft && documents && videos && drafts) {
const populatedLessons = draft.draftLessons.map((lesson, index) => { const populatedLessons = draft.draftLessons.map((lesson, index) => {
if (lesson?.resource) { if (lesson?.resource) {
const matchingResource = resources.find((resource) => resource.d === lesson.resource.d); const matchingResource = documents.find((resource) => resource.d === lesson.resource.d);
return { ...parseEvent(matchingResource), index }; const matchingParsedResource = parseEvent(matchingResource);
return { ...matchingParsedResource, index };
} else if (lesson?.draft) { } else if (lesson?.draft) {
const matchingDraft = drafts.find((draft) => draft.id === lesson.draft.id); const matchingDraft = drafts.find((draft) => draft.id === lesson.draft.id);
return { ...matchingDraft, index }; return { ...matchingDraft, index };
@ -46,24 +47,15 @@ const CourseForm = ({ draft = null }) => {
setLessons(populatedLessons); setLessons(populatedLessons);
} }
}, [draft, resources, videos, drafts]); }, [draft, documents, videos, drafts]);
useEffect(() => { useEffect(() => {
console.log('allContent', allContent); if (!documentsLoading && !videosLoading && !draftsLoading) {
}, [allContent]);
useEffect(() => {
console.log('fasfsa', videos)
}, [videos])
useEffect(() => {
if (!resourcesLoading && !videosLoading && !draftsLoading) {
let combinedContent = []; let combinedContent = [];
if (resources) { if (documents) {
combinedContent = [...combinedContent, ...resources]; combinedContent = [...combinedContent, ...documents];
} }
if (videos) { if (videos) {
console.log('workssdfsdfdsf', videos)
combinedContent = [...combinedContent, ...videos]; combinedContent = [...combinedContent, ...videos];
} }
if (drafts) { if (drafts) {
@ -71,7 +63,7 @@ const CourseForm = ({ draft = null }) => {
} }
setAllContent(combinedContent); setAllContent(combinedContent);
} }
}, [resources, videos, drafts, resourcesLoading, videosLoading, draftsLoading]); }, [documents, videos, drafts, documentsLoading, videosLoading, draftsLoading]);
const handleSubmit = async (event) => { const handleSubmit = async (event) => {
event.preventDefault(); event.preventDefault();
@ -171,7 +163,7 @@ const CourseForm = ({ draft = null }) => {
} }
}; };
if (resourcesLoading || videosLoading || draftsLoading) { if (documentsLoading || videosLoading || draftsLoading) {
return <ProgressSpinner />; return <ProgressSpinner />;
} }

View File

@ -3,14 +3,14 @@ import { Dropdown } from 'primereact/dropdown';
import GenericButton from '@/components/buttons/GenericButton'; import GenericButton from '@/components/buttons/GenericButton';
import { Dialog } from 'primereact/dialog'; import { Dialog } from 'primereact/dialog';
import { Accordion, AccordionTab } from 'primereact/accordion'; import { Accordion, AccordionTab } from 'primereact/accordion';
import EmbeddedResourceForm from '@/components/forms/course/embedded/EmbeddedResourceForm'; import EmbeddedDocumentForm from '@/components/forms/course/embedded/EmbeddedDocumentForm';
import EmbeddedVideoForm from '@/components/forms/course/embedded/EmbeddedVideoForm'; import EmbeddedVideoForm from '@/components/forms/course/embedded/EmbeddedVideoForm';
import ContentDropdownItem from '@/components/content/dropdowns/ContentDropdownItem'; import ContentDropdownItem from '@/components/content/dropdowns/ContentDropdownItem';
import SelectedContentItem from '@/components/content/SelectedContentItem'; import SelectedContentItem from '@/components/content/SelectedContentItem';
import { parseEvent } from '@/utils/nostr'; import { parseEvent } from '@/utils/nostr';
const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent, onNewResourceCreate, onNewVideoCreate }) => { const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent, onNewResourceCreate, onNewVideoCreate }) => {
const [showResourceForm, setShowResourceForm] = useState(false); const [showDocumentForm, setShowDocumentForm] = useState(false);
const [showVideoForm, setShowVideoForm] = useState(false); const [showVideoForm, setShowVideoForm] = useState(false);
const [contentOptions, setContentOptions] = useState([]); const [contentOptions, setContentOptions] = useState([]);
const [openTabs, setOpenTabs] = useState([]); const [openTabs, setOpenTabs] = useState([]);
@ -47,7 +47,7 @@ const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent, onNewRe
console.log('filtered content', filteredContent) console.log('filtered content', filteredContent)
const draftResourceOptions = filteredContent.filter(content => content?.topics.includes('resource') && !content.kind).map(content => ({ const draftDocumentOptions = filteredContent.filter(content => content?.topics.includes('document') && !content.kind).map(content => ({
label: content.title, label: content.title,
value: content value: content
})); }));
@ -57,7 +57,7 @@ const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent, onNewRe
value: content value: content
})); }));
const resourceOptions = filteredContent.filter(content => content?.topics.includes('resource') && content.kind).map(content => ({ const documentOptions = filteredContent.filter(content => content?.topics.includes('document') && content.kind).map(content => ({
label: content.title, label: content.title,
value: content value: content
})); }));
@ -69,16 +69,16 @@ const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent, onNewRe
setContentOptions([ setContentOptions([
{ {
label: 'Draft Resources', label: 'Draft Documents',
items: draftResourceOptions items: draftDocumentOptions
}, },
{ {
label: 'Draft Videos', label: 'Draft Videos',
items: draftVideoOptions items: draftVideoOptions
}, },
{ {
label: 'Published Resources', label: 'Published Documents',
items: resourceOptions items: documentOptions
}, },
{ {
label: 'Published Videos', label: 'Published Videos',
@ -116,16 +116,15 @@ const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent, onNewRe
setLessons([...lessons, { index: lessons.length }]); setLessons([...lessons, { index: lessons.length }]);
}; };
const handleNewResourceSave = async (newResource) => { const handleNewDocumentSave = async (newDocument) => {
const createdResource = await onNewResourceCreate(newResource); const createdDocument = await onNewDocumentCreate(newDocument);
if (createdResource) { if (createdDocument) {
handleContentSelect(createdResource, lessons.length); handleContentSelect(createdDocument, lessons.length);
setShowResourceForm(false); setShowDocumentForm(false);
} }
}; };
const handleNewVideoSave = async (newVideo) => { const handleNewVideoSave = async (newVideo) => {
console.log('newVideo', newVideo);
const createdVideo = await onNewVideoCreate(newVideo); const createdVideo = await onNewVideoCreate(newVideo);
if (createdVideo) { if (createdVideo) {
handleContentSelect(createdVideo, lessons.length); handleContentSelect(createdVideo, lessons.length);
@ -167,7 +166,7 @@ const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent, onNewRe
<div className="flex mt-4"> <div className="flex mt-4">
{lesson.id ? null : ( {lesson.id ? null : (
<> <>
<GenericButton label="New Resource" onClick={(e) => {e.preventDefault(); setShowResourceForm(true)}} className="mr-2" /> <GenericButton label="New Document" onClick={(e) => {e.preventDefault(); setShowDocumentForm(true)}} className="mr-2" />
<GenericButton label="New Video" onClick={(e) => {e.preventDefault(); setShowVideoForm(true)}} className="mr-2" /> <GenericButton label="New Video" onClick={(e) => {e.preventDefault(); setShowVideoForm(true)}} className="mr-2" />
</> </>
)} )}
@ -187,11 +186,11 @@ const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent, onNewRe
label="Add New Lesson" label="Add New Lesson"
onClick={addNewLesson} onClick={addNewLesson}
className="mt-4" className="mt-4"
type="button" // Explicitly set type to "button" type="button"
/> />
<Dialog className='w-full max-w-screen-md' visible={showResourceForm} onHide={() => setShowResourceForm(false)} header="Create New Resource"> <Dialog className='w-full max-w-screen-md' visible={showDocumentForm} onHide={() => setShowDocumentForm(false)} header="Create New Document">
<EmbeddedResourceForm onSave={handleNewResourceSave} isPaid={isPaidCourse} /> <EmbeddedDocumentForm onSave={handleNewDocumentSave} isPaid={isPaidCourse} />
</Dialog> </Dialog>
<Dialog className='w-full max-w-screen-md' visible={showVideoForm} onHide={() => setShowVideoForm(false)} header="Create New Video"> <Dialog className='w-full max-w-screen-md' visible={showVideoForm} onHide={() => setShowVideoForm(false)} header="Create New Video">

View File

@ -19,7 +19,7 @@ import 'primeicons/primeicons.css';
import { Tooltip } from 'primereact/tooltip'; import { Tooltip } from 'primereact/tooltip';
import 'primereact/resources/primereact.min.css'; import 'primereact/resources/primereact.min.css';
const EmbeddedResourceForm = ({ draft = null, isPublished = false, onSave, isPaid }) => { const EmbeddedDocumentForm = ({ draft = null, isPublished = false, onSave, isPaid }) => {
const [title, setTitle] = useState(draft?.title || ''); const [title, setTitle] = useState(draft?.title || '');
const [summary, setSummary] = useState(draft?.summary || ''); const [summary, setSummary] = useState(draft?.summary || '');
const [isPaidResource, setIsPaidResource] = useState(isPaid); const [isPaidResource, setIsPaidResource] = useState(isPaid);
@ -96,11 +96,11 @@ const EmbeddedResourceForm = ({ draft = null, isPublished = false, onSave, isPai
const payload = { const payload = {
title, title,
summary, summary,
type: 'resource', type: 'document',
price: isPaidResource ? price : null, price: isPaidResource ? price : null,
content, content,
image: coverImage, image: coverImage,
topics: [...new Set([...topics.map(topic => topic.trim().toLowerCase()), 'resource'])], topics: [...new Set([...topics.map(topic => topic.trim().toLowerCase()), 'document'])],
additionalLinks: additionalLinks.filter(link => link.trim() !== ''), additionalLinks: additionalLinks.filter(link => link.trim() !== ''),
user: user?.id || user?.pubkey user: user?.id || user?.pubkey
}; };
@ -108,10 +108,10 @@ const EmbeddedResourceForm = ({ draft = null, isPublished = false, onSave, isPai
if (onSave) { if (onSave) {
try { try {
await onSave(payload); await onSave(payload);
showToast('success', 'Success', draft ? 'Resource updated successfully.' : 'Resource created successfully.'); showToast('success', 'Success', draft ? 'Document updated successfully.' : 'Document created successfully.');
} catch (error) { } catch (error) {
console.error(error); console.error(error);
showToast('error', 'Error', 'Failed to save resource. Please try again.'); showToast('error', 'Error', 'Failed to save document. Please try again.');
} }
} }
}; };
@ -161,7 +161,7 @@ const EmbeddedResourceForm = ({ draft = null, isPublished = false, onSave, isPai
</div> </div>
<div className="p-inputgroup flex-1 mt-8 flex-col"> <div className="p-inputgroup flex-1 mt-8 flex-col">
<p className="py-2">Paid Resource</p> <p className="py-2">Paid Document</p>
<InputSwitch checked={isPaidResource} onChange={(e) => setIsPaidResource(e.value)} /> <InputSwitch checked={isPaidResource} onChange={(e) => setIsPaidResource(e.value)} />
{isPaidResource && ( {isPaidResource && (
<div className="p-inputgroup flex-1 py-4"> <div className="p-inputgroup flex-1 py-4">
@ -223,4 +223,4 @@ const EmbeddedResourceForm = ({ draft = null, isPublished = false, onSave, isPai
); );
} }
export default EmbeddedResourceForm; export default EmbeddedDocumentForm;

View File

@ -3,7 +3,7 @@ import { useRouter } from "next/router";
import GenericButton from "@/components/buttons/GenericButton"; import GenericButton from "@/components/buttons/GenericButton";
import MenuTab from "@/components/menutab/MenuTab"; import MenuTab from "@/components/menutab/MenuTab";
import { useCourses } from "@/hooks/nostr/useCourses"; import { useCourses } from "@/hooks/nostr/useCourses";
import { useResources } from "@/hooks/nostr/useResources"; import { useDocuments } from "@/hooks/nostr/useDocuments";
import { useVideos } from "@/hooks/nostr/useVideos"; import { useVideos } from "@/hooks/nostr/useVideos";
import { useDraftsQuery } from "@/hooks/apiQueries/useDraftsQuery"; import { useDraftsQuery } from "@/hooks/apiQueries/useDraftsQuery";
import { useCourseDraftsQuery } from "@/hooks/apiQueries/useCourseDraftsQuery"; import { useCourseDraftsQuery } from "@/hooks/apiQueries/useCourseDraftsQuery";
@ -31,7 +31,7 @@ const UserContent = () => {
const { showToast } = useToast(); const { showToast } = useToast();
const {ndk, addSigner} = useNDKContext(); const {ndk, addSigner} = useNDKContext();
const { courses, coursesLoading, coursesError } = useCourses(); const { courses, coursesLoading, coursesError } = useCourses();
const { resources, resourcesLoading, resourcesError } = useResources(); const { documents, documentsLoading, documentsError } = useDocuments();
const { videos, videosLoading, videosError } = useVideos(); const { videos, videosLoading, videosError } = useVideos();
const { courseDrafts, courseDraftsLoading, courseDraftsError } = useCourseDraftsQuery(); const { courseDrafts, courseDraftsLoading, courseDraftsError } = useCourseDraftsQuery();
const { drafts, draftsLoading, draftsError } = useDraftsQuery(); const { drafts, draftsLoading, draftsError } = useDraftsQuery();
@ -51,7 +51,7 @@ const UserContent = () => {
{ label: "Published", icon: "pi pi-verified" }, { label: "Published", icon: "pi pi-verified" },
{ label: "Drafts", icon: "pi pi-file-edit" }, { label: "Drafts", icon: "pi pi-file-edit" },
{ label: "Draft Courses", icon: "pi pi-book" }, { label: "Draft Courses", icon: "pi pi-book" },
{ label: "Resources", icon: "pi pi-file" }, { label: "Documents", icon: "pi pi-file" },
{ label: "Videos", icon: "pi pi-video" }, { label: "Videos", icon: "pi pi-video" },
{ label: "Courses", icon: "pi pi-desktop" }, { label: "Courses", icon: "pi pi-desktop" },
]; ];
@ -98,8 +98,8 @@ const UserContent = () => {
case 2: case 2:
return courseDrafts || []; return courseDrafts || [];
case 3: case 3:
return resources?.map(parseEvent) || []; return documents?.map(parseEvent) || [];
case 3: case 4:
return videos?.map(parseEvent) || []; return videos?.map(parseEvent) || [];
case 4: case 4:
return courses?.map(parseEvent) || []; return courses?.map(parseEvent) || [];
@ -110,10 +110,10 @@ const UserContent = () => {
setContent(getContentByIndex(activeIndex)); setContent(getContentByIndex(activeIndex));
} }
}, [activeIndex, isClient, drafts, resources, videos, courses, publishedContent, courseDrafts]) }, [activeIndex, isClient, drafts, documents, videos, courses, publishedContent, courseDrafts])
const isLoading = coursesLoading || resourcesLoading || videosLoading || draftsLoading || contentIdsLoading || courseDraftsLoading; const isLoading = coursesLoading || documentsLoading || videosLoading || draftsLoading || contentIdsLoading || courseDraftsLoading;
const isError = coursesError || resourcesError || videosError || draftsError || contentIdsError || courseDraftsError; const isError = coursesError || documentsError || videosError || draftsError || contentIdsError || courseDraftsError;
return ( return (
<div className="p-4"> <div className="p-4">

View File

@ -40,6 +40,7 @@ const Sidebar = ({ course = false }) => {
if (router.isReady) { if (router.isReady) {
const { slug } = router.query; const { slug } = router.query;
try {
if (slug && course) { if (slug && course) {
const { data } = nip19.decode(slug) const { data } = nip19.decode(slug)
@ -73,6 +74,9 @@ const Sidebar = ({ course = false }) => {
fetchCourse(id); fetchCourse(id);
} }
} }
} catch (err) {
console.error(err);
}
} }
}, [router.isReady, router.query, ndk, course]); }, [router.isReady, router.query, ndk, course]);
@ -136,8 +140,8 @@ const Sidebar = ({ course = false }) => {
<div onClick={() => router.push('/content?tag=videos')} className={`w-full cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/content?tag=videos') ? 'bg-gray-700' : ''}`}> <div onClick={() => router.push('/content?tag=videos')} className={`w-full cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/content?tag=videos') ? 'bg-gray-700' : ''}`}>
<p className="pl-3 rounded-md font-bold text-lg"><i className="pi pi-video text-sm pr-1"></i> Videos</p> <p className="pl-3 rounded-md font-bold text-lg"><i className="pi pi-video text-sm pr-1"></i> Videos</p>
</div> </div>
<div onClick={() => router.push('/content?tag=resources')} className={`w-full cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/content?tag=resources') ? 'bg-gray-700' : ''}`}> <div onClick={() => router.push('/content?tag=documents')} className={`w-full cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/content?tag=documents') ? 'bg-gray-700' : ''}`}>
<p className="pl-3 rounded-md font-bold text-lg"><i className="pi pi-file text-sm pr-1"></i> Resources</p> <p className="pl-3 rounded-md font-bold text-lg"><i className="pi pi-file text-sm pr-1"></i> Documents</p>
</div> </div>
</AccordionTab> </AccordionTab>
</Accordion> </Accordion>

View File

@ -4,12 +4,12 @@ import { useContentIdsQuery } from '@/hooks/apiQueries/useContentIdsQuery';
const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY; const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY;
export function useResources() { export function useDocuments() {
const [isClient, setIsClient] = useState(false); const [isClient, setIsClient] = useState(false);
const [resources, setResources] = useState(); const [documents, setDocuments] = useState();
// Add new state variables for loading and error // Add new state variables for loading and error
const [resourcesLoading, setResourcesLoading] = useState(false); const [documentsLoading, setDocumentsLoading] = useState(false);
const [resourcesError, setResourcesError] = useState(null); const [documentsError, setDocumentsError] = useState(null);
const { contentIds } = useContentIdsQuery() const { contentIds } = useContentIdsQuery()
const {ndk, addSigner} = useNDKContext(); const {ndk, addSigner} = useNDKContext();
@ -19,18 +19,18 @@ export function useResources() {
}, []); }, []);
const hasRequiredProperties = (event, contentIds) => { const hasRequiredProperties = (event, contentIds) => {
const hasResource = event.tags.some(([tag, value]) => tag === "t" && value === "resource"); const hasDocument = event.tags.some(([tag, value]) => tag === "t" && value === "document");
const hasId = event.tags.some(([tag, value]) => tag === "d" && contentIds.includes(value)); const hasId = event.tags.some(([tag, value]) => tag === "d" && contentIds.includes(value));
return hasResource && hasId; return hasDocument && hasId;
}; };
const fetchResourcesFromNDK = async () => { const fetchDocumentsFromNDK = async () => {
setResourcesLoading(true); setDocumentsLoading(true);
setResourcesError(null); setDocumentsError(null);
try { try {
if (!contentIds || contentIds.length === 0) { if (!contentIds || contentIds.length === 0) {
console.log('No content IDs found'); console.log('No content IDs found');
setResourcesLoading(false); setDocumentsLoading(false);
return []; // Return early if no content IDs are found return []; // Return early if no content IDs are found
} }
@ -41,29 +41,29 @@ export function useResources() {
if (events && events.size > 0) { if (events && events.size > 0) {
const eventsArray = Array.from(events); const eventsArray = Array.from(events);
const resources = eventsArray.filter(event => hasRequiredProperties(event, contentIds)); const documents = eventsArray.filter(event => hasRequiredProperties(event, contentIds));
setResourcesLoading(false); setDocumentsLoading(false);
return resources; return documents;
} }
setResourcesLoading(false); setDocumentsLoading(false);
return []; return [];
} catch (error) { } catch (error) {
console.error('Error fetching resources from NDK:', error); console.error('Error fetching documents from NDK:', error);
setResourcesError(error); setDocumentsError(error);
setResourcesLoading(false); setDocumentsLoading(false);
return []; return [];
} }
}; };
useEffect(() => { useEffect(() => {
if (isClient && contentIds) { if (isClient && contentIds) {
fetchResourcesFromNDK().then(fetchedResources => { fetchDocumentsFromNDK().then(fetchedDocuments => {
if (fetchedResources && fetchedResources.length > 0) { if (fetchedDocuments && fetchedDocuments.length > 0) {
setResources(fetchedResources); setDocuments(fetchedDocuments);
} }
}); });
} }
}, [isClient, contentIds]); }, [isClient, contentIds]);
return { resources, resourcesLoading, resourcesError }; return { documents, documentsLoading, documentsError };
} }

View File

@ -5,7 +5,7 @@ import axios from 'axios';
const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY; const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY;
export function useResourcesQuery() { export function useDocumentsQuery() {
const [isClient, setIsClient] = useState(false); const [isClient, setIsClient] = useState(false);
const {ndk, addSigner} = useNDKContext(); const {ndk, addSigner} = useNDKContext();
@ -14,12 +14,12 @@ export function useResourcesQuery() {
}, []); }, []);
const hasRequiredProperties = (event, contentIds) => { const hasRequiredProperties = (event, contentIds) => {
const hasResource = event.tags.some(([tag, value]) => tag === "t" && value === "resource"); const hasDocument = event.tags.some(([tag, value]) => tag === "t" && value === "document");
const hasId = event.tags.some(([tag, value]) => tag === "d" && contentIds.includes(value)); const hasId = event.tags.some(([tag, value]) => tag === "d" && contentIds.includes(value));
return hasResource && hasId; return hasDocument && hasId;
}; };
const fetchResourcesFromNDK = async () => { const fetchDocumentsFromNDK = async () => {
try { try {
const response = await axios.get(`/api/content/all`); const response = await axios.get(`/api/content/all`);
const contentIds = response.data; const contentIds = response.data;
@ -36,23 +36,23 @@ export function useResourcesQuery() {
if (events && events.size > 0) { if (events && events.size > 0) {
const eventsArray = Array.from(events); const eventsArray = Array.from(events);
const resources = eventsArray.filter(event => hasRequiredProperties(event, contentIds)); const documents = eventsArray.filter(event => hasRequiredProperties(event, contentIds));
return resources; return documents;
} }
return []; return [];
} catch (error) { } catch (error) {
console.error('Error fetching resources from NDK:', error); console.error('Error fetching documents from NDK:', error);
return []; return [];
} }
}; };
const { data: resources, isLoading: resourcesLoading, error: resourcesError, refetch: refetchResources } = useQuery({ const { data: documents, isLoading: documentsLoading, error: documentsError, refetch: refetchDocuments } = useQuery({
queryKey: ['resources', isClient], queryKey: ['documents', isClient],
queryFn: fetchResourcesFromNDK, queryFn: fetchDocumentsFromNDK,
// staleTime: 1000 * 60 * 30, // 30 minutes // staleTime: 1000 * 60 * 30, // 30 minutes
// refetchInterval: 1000 * 60 * 30, // 30 minutes // refetchInterval: 1000 * 60 * 30, // 30 minutes
enabled: isClient, enabled: isClient,
}); });
return { resources, resourcesLoading, resourcesError, refetchResources }; return { documents, documentsLoading, documentsError, refetchDocuments };
} }

View File

@ -29,7 +29,7 @@ export default function MyApp({
const router = useRouter(); const router = useRouter();
useEffect(() => { useEffect(() => {
setIsCourseView(router.pathname.includes('course')); setIsCourseView(router.pathname.includes('course') && !router.pathname.includes('draft'));
}, [router.pathname]); }, [router.pathname]);
// const [sidebarExpanded, setSidebarExpanded] = useState(true); // const [sidebarExpanded, setSidebarExpanded] = useState(true);

View File

@ -60,9 +60,9 @@ const AboutPage = () => {
title="Content Types" title="Content Types"
description={ description={
<ul className="list-disc list-inside ml-6 space-y-2"> <ul className="list-disc list-inside ml-6 space-y-2">
<li><span className="font-bold">Resources:</span> Markdown documents posted as NIP-23 long-form events on Nostr.</li> <li><span className="font-bold">Documents:</span> Markdown documents posted as NIP-23 long-form events on Nostr.</li>
<li><span className="font-bold">Videos:</span> Enhanced markdown files with rich media support, including embedded videos, also saved as NIP-23 events.</li> <li><span className="font-bold">Videos:</span> Enhanced markdown files with rich media support, including embedded videos, also saved as NIP-23 events.</li>
<li><span className="font-bold">Courses:</span> Nostr lists that combine multiple resources and videos into a structured learning path.</li> <li><span className="font-bold">Courses:</span> Nostr lists that combine multiple documents and videos into a structured learning path.</li>
</ul> </ul>
} }
/> />

View File

@ -1,7 +1,7 @@
import React, { useEffect, useState, useMemo } from 'react'; import React, { useEffect, useState, useMemo } from 'react';
import GenericCarousel from '@/components/content/carousels/GenericCarousel'; import GenericCarousel from '@/components/content/carousels/GenericCarousel';
import { parseEvent, parseCourseEvent } from '@/utils/nostr'; import { parseEvent, parseCourseEvent } from '@/utils/nostr';
import { useResources } from '@/hooks/nostr/useResources'; import { useDocuments } from '@/hooks/nostr/useDocuments';
import { useVideos } from '@/hooks/nostr/useVideos'; import { useVideos } from '@/hooks/nostr/useVideos';
import { useCourses } from '@/hooks/nostr/useCourses'; import { useCourses } from '@/hooks/nostr/useCourses';
import { TabMenu } from 'primereact/tabmenu'; import { TabMenu } from 'primereact/tabmenu';
@ -17,7 +17,7 @@ const MenuTab = ({ items, selectedTopic, onTabChange }) => {
const menuItems = allItems.map((item, index) => { const menuItems = allItems.map((item, index) => {
let icon = 'pi pi-tag'; let icon = 'pi pi-tag';
if (item === 'All') icon = 'pi pi-eye'; if (item === 'All') icon = 'pi pi-eye';
else if (item === 'Resources') icon = 'pi pi-file'; else if (item === 'Documents') icon = 'pi pi-file';
else if (item === 'Videos') icon = 'pi pi-video'; else if (item === 'Videos') icon = 'pi pi-video';
else if (item === 'Courses') icon = 'pi pi-desktop'; else if (item === 'Courses') icon = 'pi pi-desktop';
@ -67,11 +67,11 @@ const MenuTab = ({ items, selectedTopic, onTabChange }) => {
const ContentPage = () => { const ContentPage = () => {
const router = useRouter(); const router = useRouter();
const { resources, resourcesLoading } = useResources(); const { documents, documentsLoading } = useDocuments();
const { videos, videosLoading } = useVideos(); const { videos, videosLoading } = useVideos();
const { courses, coursesLoading } = useCourses(); const { courses, coursesLoading } = useCourses();
const [processedResources, setProcessedResources] = useState([]); const [processedDocuments, setProcessedDocuments] = useState([]);
const [processedVideos, setProcessedVideos] = useState([]); const [processedVideos, setProcessedVideos] = useState([]);
const [processedCourses, setProcessedCourses] = useState([]); const [processedCourses, setProcessedCourses] = useState([]);
const [allContent, setAllContent] = useState([]); const [allContent, setAllContent] = useState([]);
@ -92,11 +92,11 @@ const ContentPage = () => {
}, [router.query.tag]); }, [router.query.tag]);
useEffect(() => { useEffect(() => {
if (resources && !resourcesLoading) { if (documents && !documentsLoading) {
const processedResources = resources.map(resource => ({...parseEvent(resource), type: 'resource'})); const processedDocuments = documents.map(document => ({...parseEvent(document), type: 'document'}));
setProcessedResources(processedResources); setProcessedDocuments(processedDocuments);
} }
}, [resources, resourcesLoading]); }, [documents, documentsLoading]);
useEffect(() => { useEffect(() => {
if (videos && !videosLoading) { if (videos && !videosLoading) {
@ -113,11 +113,11 @@ const ContentPage = () => {
}, [courses, coursesLoading]); }, [courses, coursesLoading]);
useEffect(() => { useEffect(() => {
const allContent = [...processedResources, ...processedVideos, ...processedCourses]; const allContent = [...processedDocuments, ...processedVideos, ...processedCourses];
setAllContent(allContent); setAllContent(allContent);
const uniqueTopics = new Set(allContent.map(item => item.topics).flat()); const uniqueTopics = new Set(allContent.map(item => item.topics).flat());
const priorityItems = ['All', 'Courses', 'Videos', 'Resources']; const priorityItems = ['All', 'Courses', 'Videos', 'Documents'];
const otherTopics = Array.from(uniqueTopics).filter(topic => !priorityItems.includes(topic)); const otherTopics = Array.from(uniqueTopics).filter(topic => !priorityItems.includes(topic));
const combinedTopics = [...priorityItems.slice(1), ...otherTopics]; const combinedTopics = [...priorityItems.slice(1), ...otherTopics];
setAllTopics(combinedTopics); setAllTopics(combinedTopics);
@ -125,13 +125,13 @@ const ContentPage = () => {
if (selectedTopic) { if (selectedTopic) {
filterContent(selectedTopic, allContent); filterContent(selectedTopic, allContent);
} }
}, [processedResources, processedVideos, processedCourses]); }, [processedDocuments, processedVideos, processedCourses]);
const filterContent = (topic, content) => { const filterContent = (topic, content) => {
let filtered = content; let filtered = content;
if (topic !== 'All') { if (topic !== 'All') {
const topicLower = topic.toLowerCase(); const topicLower = topic.toLowerCase();
if (['courses', 'videos', 'resources'].includes(topicLower)) { if (['courses', 'videos', 'documents'].includes(topicLower)) {
filtered = content.filter(item => item.type === topicLower.slice(0, -1)); filtered = content.filter(item => item.type === topicLower.slice(0, -1));
} else { } else {
filtered = content.filter(item => item.topics && item.topics.includes(topic.toLowerCase())); filtered = content.filter(item => item.topics && item.topics.includes(topic.toLowerCase()));
@ -166,7 +166,7 @@ const ContentPage = () => {
<h1 className="text-3xl font-bold mb-4 ml-1">All Content</h1> <h1 className="text-3xl font-bold mb-4 ml-1">All Content</h1>
</div> </div>
<MenuTab <MenuTab
items={['Courses', 'Videos', 'Resources', ...allTopics.filter(topic => !['Courses', 'Videos', 'Resources'].includes(topic))]} items={['Courses', 'Videos', 'Documents', ...allTopics.filter(topic => !['Courses', 'Videos', 'Documents'].includes(topic))]}
selectedTopic={selectedTopic} selectedTopic={selectedTopic}
onTabChange={handleTopicChange} onTabChange={handleTopicChange}
className="max-w-[90%] mx-auto" className="max-w-[90%] mx-auto"

View File

@ -44,7 +44,6 @@ const DraftCourse = () => {
axios.get(`/api/courses/drafts/${slug}`) axios.get(`/api/courses/drafts/${slug}`)
.then(res => { .then(res => {
setCourse(res.data); setCourse(res.data);
console.log('coursesssss:', res.data);
setLessons(res.data.draftLessons); setLessons(res.data.draftLessons);
}) })
.catch(err => { .catch(err => {

View File

@ -53,16 +53,19 @@ const useCourseData = (ndk, fetchAuthor, router) => {
return { course, lessonIds }; return { course, lessonIds };
}; };
const useLessons = (ndk, fetchAuthor, lessonIds) => { const useLessons = (ndk, fetchAuthor, lessonIds, pubkey) => {
const [lessons, setLessons] = useState([]); const [lessons, setLessons] = useState([]);
const [uniqueLessons, setUniqueLessons] = useState([]); const [uniqueLessons, setUniqueLessons] = useState([]);
console.log('lessonIds', lessonIds);
useEffect(() => { useEffect(() => {
if (lessonIds.length > 0) { if (lessonIds.length > 0) {
const fetchLesson = async (lessonId) => { const fetchLesson = async (lessonId) => {
console.log('lessonId', lessonId);
try { try {
await ndk.connect(); await ndk.connect();
const filter = { "#d": [lessonId] }; const filter = { "#d": [lessonId], kinds:[30023, 30402], authors: [pubkey] };
const event = await ndk.fetchEvent(filter); const event = await ndk.fetchEvent(filter);
if (event) { if (event) {
const author = await fetchAuthor(event.pubkey); const author = await fetchAuthor(event.pubkey);
@ -83,6 +86,10 @@ const useLessons = (ndk, fetchAuthor, lessonIds) => {
setUniqueLessons(newUniqueLessons); setUniqueLessons(newUniqueLessons);
}, [lessons]); }, [lessons]);
useEffect(() => {
console.log('uniqueLessons', uniqueLessons);
}, [uniqueLessons]);
return { lessons, uniqueLessons, setLessons }; return { lessons, uniqueLessons, setLessons };
}; };
@ -139,7 +146,7 @@ const Course = () => {
}, [ndk]); }, [ndk]);
const { course, lessonIds } = useCourseData(ndk, fetchAuthor, router); const { course, lessonIds } = useCourseData(ndk, fetchAuthor, router);
const { lessons, uniqueLessons, setLessons } = useLessons(ndk, fetchAuthor, lessonIds); const { lessons, uniqueLessons, setLessons } = useLessons(ndk, fetchAuthor, lessonIds, course?.pubkey);
const { decryptionPerformed, loading } = useDecryption(session, paidCourse, course, lessons, setLessons); const { decryptionPerformed, loading } = useDecryption(session, paidCourse, course, lessons, setLessons);
useEffect(() => { useEffect(() => {

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import MenuTab from "@/components/menutab/MenuTab"; import MenuTab from "@/components/menutab/MenuTab";
import ResourceForm from "@/components/forms/ResourceForm"; import DocumentForm from "@/components/forms/DocumentForm";
import VideoForm from "@/components/forms/VideoForm"; import VideoForm from "@/components/forms/VideoForm";
import CourseForm from "@/components/forms/course/CourseForm"; import CourseForm from "@/components/forms/course/CourseForm";
import { useIsAdmin } from "@/hooks/useIsAdmin"; import { useIsAdmin } from "@/hooks/useIsAdmin";
@ -12,7 +12,7 @@ const Create = () => {
const { isAdmin, isLoading } = useIsAdmin(); const { isAdmin, isLoading } = useIsAdmin();
const router = useRouter(); const router = useRouter();
const homeItems = [ const homeItems = [
{ label: 'Resource', icon: 'pi pi-book' }, { label: 'Document', icon: 'pi pi-file' },
{ label: 'Video', icon: 'pi pi-video' }, { label: 'Video', icon: 'pi pi-video' },
{ label: 'Course', icon: 'pi pi-desktop' } { label: 'Course', icon: 'pi pi-desktop' }
]; ];
@ -32,8 +32,8 @@ const Create = () => {
return <CourseForm />; return <CourseForm />;
case 'Video': case 'Video':
return <VideoForm />; return <VideoForm />;
case 'Resource': case 'Document':
return <ResourceForm />; return <DocumentForm />;
default: default:
return null; // or a default component return null; // or a default component
} }

View File

@ -1,7 +1,7 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { parseEvent } from "@/utils/nostr"; import { parseEvent } from "@/utils/nostr";
import ResourceForm from "@/components/forms/ResourceForm"; import DocumentForm from "@/components/forms/DocumentForm";
import VideoForm from "@/components/forms/VideoForm"; import VideoForm from "@/components/forms/VideoForm";
import CourseForm from "@/components/forms/course/CourseForm"; import CourseForm from "@/components/forms/course/CourseForm";
import { useNDKContext } from "@/context/NDKContext"; import { useNDKContext } from "@/context/NDKContext";
@ -41,7 +41,7 @@ export default function Edit() {
<h2 className="text-center mb-8">Edit Published Event</h2> <h2 className="text-center mb-8">Edit Published Event</h2>
{event?.topics.includes('course') && <CourseForm draft={event} isPublished />} {event?.topics.includes('course') && <CourseForm draft={event} isPublished />}
{!event?.topics.includes('video') && <VideoForm draft={event} isPublished />} {!event?.topics.includes('video') && <VideoForm draft={event} isPublished />}
{event?.topics.includes('resource') && <ResourceForm draft={event} isPublished />} {event?.topics.includes('document') && <DocumentForm draft={event} isPublished />}
</div> </div>
); );
} }

View File

@ -1,7 +1,7 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import axios from "axios"; import axios from "axios";
import ResourceForm from "@/components/forms/ResourceForm"; import DocumentForm from "@/components/forms/DocumentForm";
import VideoForm from "@/components/forms/VideoForm"; import VideoForm from "@/components/forms/VideoForm";
import CourseForm from "@/components/forms/course/CourseForm"; import CourseForm from "@/components/forms/course/CourseForm";
import { useIsAdmin } from "@/hooks/useIsAdmin"; import { useIsAdmin } from "@/hooks/useIsAdmin";
@ -38,7 +38,7 @@ const Edit = () => {
<h2 className="text-center mb-8">Edit Draft</h2> <h2 className="text-center mb-8">Edit Draft</h2>
{draft?.type === 'course' && <CourseForm draft={draft} />} {draft?.type === 'course' && <CourseForm draft={draft} />}
{draft?.type === 'video' && <VideoForm draft={draft} />} {draft?.type === 'video' && <VideoForm draft={draft} />}
{draft?.type === 'resource' && <ResourceForm draft={draft} />} {draft?.type === 'document' && <DocumentForm draft={draft} />}
</div> </div>
); );
}; };

View File

@ -32,6 +32,7 @@ export default function Draft() {
const { returnImageProxy } = useImageProxy(); const { returnImageProxy } = useImageProxy();
const { data: session, status } = useSession(); const { data: session, status } = useSession();
const [user, setUser] = useState(null); const [user, setUser] = useState(null);
const [nAddress, setNAddress] = useState(null);
const [videoId, setVideoId] = useState(null); const [videoId, setVideoId] = useState(null);
const { width, height } = useResponsiveImageDimensions(); const { width, height } = useResponsiveImageDimensions();
const router = useRouter(); const router = useRouter();
@ -187,7 +188,7 @@ export default function Draft() {
console.log('NewDTag:', NewDTag); console.log('NewDTag:', NewDTag);
switch (draft?.type) { switch (draft?.type) {
case 'resource': case 'document':
if (draft?.price) { if (draft?.price) {
// encrypt the content with NEXT_PUBLIC_APP_PRIV_KEY to NEXT_PUBLIC_APP_PUBLIC_KEY // encrypt the content with NEXT_PUBLIC_APP_PRIV_KEY to NEXT_PUBLIC_APP_PUBLIC_KEY
encryptedContent = await nip04.encrypt(process.env.NEXT_PUBLIC_APP_PRIV_KEY, process.env.NEXT_PUBLIC_APP_PUBLIC_KEY, draft.content); encryptedContent = await nip04.encrypt(process.env.NEXT_PUBLIC_APP_PRIV_KEY, process.env.NEXT_PUBLIC_APP_PUBLIC_KEY, draft.content);
@ -208,7 +209,7 @@ export default function Draft() {
...(draft?.additionalLinks ? draft.additionalLinks.map(link => ['r', link]) : []), ...(draft?.additionalLinks ? draft.additionalLinks.map(link => ['r', link]) : []),
]; ];
type = 'resource'; type = 'document';
break; break;
case 'video': case 'video':
if (draft?.price) { if (draft?.price) {
@ -315,7 +316,7 @@ export default function Draft() {
</div> </div>
<div className='flex flex-col max-tab:mt-12 max-mob:mt-12'> <div className='flex flex-col max-tab:mt-12 max-mob:mt-12'>
{draft && ( {draft && (
<div style={{ width: width < 768 ? "auto" : width }} onClick={() => router.push(`/details/${draft.id}`)} className="flex flex-col items-center mx-auto cursor-pointer rounded-md shadow-lg"> <div style={{ width: width < 768 ? "auto" : width }} onClick={() => router.push(`/details/${nAddress}`)} className="flex flex-col items-center mx-auto cursor-pointer rounded-md shadow-lg">
<div style={{ maxWidth: width, minWidth: width }} className="max-tab:h-auto max-mob:h-auto"> <div style={{ maxWidth: width, minWidth: width }} className="max-tab:h-auto max-mob:h-auto">
<Image <Image
alt="resource thumbnail" alt="resource thumbnail"

View File

@ -2,7 +2,7 @@ import Head from 'next/head';
import React from 'react'; import React from 'react';
import CoursesCarousel from '@/components/content/carousels/CoursesCarousel'; import CoursesCarousel from '@/components/content/carousels/CoursesCarousel';
import VideosCarousel from '@/components/content/carousels/VideosCarousel'; import VideosCarousel from '@/components/content/carousels/VideosCarousel';
import ResourcesCarousel from '@/components/content/carousels/ResourcesCarousel'; import DocumentsCarousel from '@/components/content/carousels/DocumentsCarousel';
import InteractivePromotionalCarousel from '@/components/content/carousels/InteractivePromotionalCarousel'; import InteractivePromotionalCarousel from '@/components/content/carousels/InteractivePromotionalCarousel';
export default function Home() { export default function Home() {
@ -18,7 +18,7 @@ export default function Home() {
<InteractivePromotionalCarousel /> <InteractivePromotionalCarousel />
<CoursesCarousel /> <CoursesCarousel />
<VideosCarousel /> <VideosCarousel />
<ResourcesCarousel /> <DocumentsCarousel />
</main> </main>
</> </>
); );

View File

@ -63,7 +63,7 @@ export const parseEvent = (event) => {
image: '', image: '',
published_at: '', published_at: '',
topics: [], // Added to hold all topics topics: [], // Added to hold all topics
type: 'resource', // Default type type: 'document', // Default type
}; };
// Iterate over the tags array to extract data // Iterate over the tags array to extract data