mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-06 18:31:00 +00:00
Fixing up course display, course details and lesson details under courses
This commit is contained in:
parent
b61e927c0c
commit
7b69ccfb66
@ -1,8 +1,5 @@
|
|||||||
"use client";
|
import React, { useEffect, useState, useCallback } from 'react';
|
||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useNostr } from '@/hooks/useNostr';
|
|
||||||
import { findKind0Fields } from '@/utils/nostr';
|
|
||||||
import { useImageProxy } from '@/hooks/useImageProxy';
|
import { useImageProxy } from '@/hooks/useImageProxy';
|
||||||
import ZapDisplay from '@/components/zaps/ZapDisplay';
|
import ZapDisplay from '@/components/zaps/ZapDisplay';
|
||||||
import { getSatAmountFromInvoice } from '@/utils/lightning';
|
import { getSatAmountFromInvoice } from '@/utils/lightning';
|
||||||
@ -12,7 +9,11 @@ import { useLocalStorageWithEffect } from '@/hooks/useLocalStorage';
|
|||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import ZapThreadsWrapper from '@/components/ZapThreadsWrapper';
|
import ZapThreadsWrapper from '@/components/ZapThreadsWrapper';
|
||||||
|
import { useNDKContext } from "@/context/NDKContext";
|
||||||
|
import { useZapsSubscription } from '@/hooks/nostrQueries/zaps/useZapsSubscription';
|
||||||
|
import { findKind0Fields } from '@/utils/nostr';
|
||||||
import 'primeicons/primeicons.css';
|
import 'primeicons/primeicons.css';
|
||||||
|
|
||||||
const MDDisplay = dynamic(
|
const MDDisplay = dynamic(
|
||||||
() => import("@uiw/react-markdown-preview"),
|
() => import("@uiw/react-markdown-preview"),
|
||||||
{
|
{
|
||||||
@ -27,25 +28,36 @@ const BitcoinConnectPayButton = dynamic(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export default function CourseDetails({processedEvent}) {
|
export default function CourseDetails({ processedEvent }) {
|
||||||
const [author, setAuthor] = useState(null);
|
const [author, setAuthor] = useState(null);
|
||||||
const [bitcoinConnect, setBitcoinConnect] = useState(false);
|
const [bitcoinConnect, setBitcoinConnect] = useState(false);
|
||||||
const [nAddress, setNAddress] = useState(null);
|
const [nAddress, setNAddress] = useState(null);
|
||||||
const [user] = useLocalStorageWithEffect('user', {});
|
|
||||||
const [zaps, setZaps] = useState([]);
|
|
||||||
const [zapAmount, setZapAmount] = useState(0);
|
const [zapAmount, setZapAmount] = useState(0);
|
||||||
const { returnImageProxy } = useImageProxy();
|
|
||||||
const { fetchKind0, zapEvent, fetchZapsForEvent } = useNostr();
|
|
||||||
|
|
||||||
|
const [user] = useLocalStorageWithEffect('user', {});
|
||||||
|
const { zaps, zapsLoading, zapsError } = useZapsSubscription({ event: processedEvent });
|
||||||
|
const { returnImageProxy } = useImageProxy();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const ndk = useNDKContext();
|
||||||
|
|
||||||
const handleZapEvent = async () => {
|
const handleZapEvent = async () => {
|
||||||
if (!processedEvent) return;
|
if (!processedEvent) return;
|
||||||
|
|
||||||
|
// Update zap event logic if necessary for NDK
|
||||||
const response = await zapEvent(processedEvent);
|
const response = await zapEvent(processedEvent);
|
||||||
|
|
||||||
console.log('zap response:', response);
|
console.log('zap response:', response);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchAuthor = useCallback(async (pubkey) => {
|
||||||
|
const author = await ndk.getUser({ pubkey });
|
||||||
|
const profile = await author.fetchProfile();
|
||||||
|
const fields = await findKind0Fields(profile);
|
||||||
|
console.log('fields:', fields);
|
||||||
|
if (fields) {
|
||||||
|
setAuthor(fields);
|
||||||
}
|
}
|
||||||
|
}, [ndk]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window === 'undefined') return;
|
if (typeof window === 'undefined') return;
|
||||||
@ -58,18 +70,10 @@ export default function CourseDetails({processedEvent}) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchAuthor = async (pubkey) => {
|
|
||||||
const author = await fetchKind0(pubkey);
|
|
||||||
const fields = await findKind0Fields(author);
|
|
||||||
console.log('fields:', fields);
|
|
||||||
if (fields) {
|
|
||||||
setAuthor(fields);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (processedEvent) {
|
if (processedEvent) {
|
||||||
fetchAuthor(processedEvent.pubkey);
|
fetchAuthor(processedEvent.pubkey);
|
||||||
}
|
}
|
||||||
}, [fetchKind0, processedEvent]);
|
}, [fetchAuthor, processedEvent]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (processedEvent?.d) {
|
if (processedEvent?.d) {
|
||||||
@ -97,16 +101,6 @@ export default function CourseDetails({processedEvent}) {
|
|||||||
setZapAmount(total);
|
setZapAmount(total);
|
||||||
}, [zaps]);
|
}, [zaps]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchZaps = async () => {
|
|
||||||
if (processedEvent) {
|
|
||||||
const zaps = await fetchZapsForEvent(processedEvent);
|
|
||||||
setZaps(zaps);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fetchZaps();
|
|
||||||
}, [fetchZapsForEvent, processedEvent]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full px-24 pt-12 mx-auto mt-4 max-tab:px-0 max-mob:px-0 max-tab:pt-2 max-mob:pt-2'>
|
<div className='w-full px-24 pt-12 mx-auto mt-4 max-tab:px-0 max-mob:px-0 max-tab:pt-2 max-mob:pt-2'>
|
||||||
<div className='w-full flex flex-row justify-between max-tab:flex-col max-mob:flex-col'>
|
<div className='w-full flex flex-row justify-between max-tab:flex-col max-mob:flex-col'>
|
||||||
@ -118,8 +112,7 @@ export default function CourseDetails({processedEvent}) {
|
|||||||
processedEvent.topics.map((topic, index) => (
|
processedEvent.topics.map((topic, index) => (
|
||||||
<Tag className='mr-2 text-white' key={index} value={topic}></Tag>
|
<Tag className='mr-2 text-white' key={index} value={topic}></Tag>
|
||||||
))
|
))
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<h1 className='text-4xl mt-6'>{processedEvent?.title}</h1>
|
<h1 className='text-4xl mt-6'>{processedEvent?.title}</h1>
|
||||||
<p className='text-xl mt-6'>{processedEvent?.summary}</p>
|
<p className='text-xl mt-6'>{processedEvent?.summary}</p>
|
||||||
@ -134,7 +127,7 @@ export default function CourseDetails({processedEvent}) {
|
|||||||
<p className='text-lg'>
|
<p className='text-lg'>
|
||||||
Created by{' '}
|
Created by{' '}
|
||||||
<a rel='noreferrer noopener' target='_blank' className='text-blue-500 hover:underline'>
|
<a rel='noreferrer noopener' target='_blank' className='text-blue-500 hover:underline'>
|
||||||
{author?.username}
|
{author?.username || author?.name || author?.pubkey}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -155,7 +148,7 @@ export default function CourseDetails({processedEvent}) {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className='w-full flex justify-end'>
|
<div className='w-full flex justify-end'>
|
||||||
<ZapDisplay zapAmount={zapAmount} event={processedEvent} />
|
<ZapDisplay zapAmount={zapAmount} event={processedEvent} zapsLoading={zapsLoading} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,12 +2,10 @@ import React, { useEffect, useState } from "react";
|
|||||||
import { Tag } from "primereact/tag";
|
import { Tag } from "primereact/tag";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useImageProxy } from "@/hooks/useImageProxy";
|
import { useImageProxy } from "@/hooks/useImageProxy";
|
||||||
import { useNostr } from "@/hooks/useNostr";
|
|
||||||
import { getSatAmountFromInvoice } from "@/utils/lightning";
|
import { getSatAmountFromInvoice } from "@/utils/lightning";
|
||||||
import { parseEvent } from "@/utils/nostr";
|
|
||||||
import { useLocalStorageWithEffect } from "@/hooks/useLocalStorage";
|
|
||||||
import ZapDisplay from "@/components/zaps/ZapDisplay";
|
import ZapDisplay from "@/components/zaps/ZapDisplay";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
|
import { useZapsQuery } from "@/hooks/nostrQueries/zaps/useZapsQuery";
|
||||||
|
|
||||||
const BitcoinConnectPayButton = dynamic(
|
const BitcoinConnectPayButton = dynamic(
|
||||||
() => import('@getalby/bitcoin-connect-react').then((mod) => mod.PayButton),
|
() => import('@getalby/bitcoin-connect-react').then((mod) => mod.PayButton),
|
||||||
@ -25,10 +23,9 @@ const MDDisplay = dynamic(
|
|||||||
|
|
||||||
const CourseLesson = ({ lesson, course }) => {
|
const CourseLesson = ({ lesson, course }) => {
|
||||||
const [bitcoinConnect, setBitcoinConnect] = useState(false);
|
const [bitcoinConnect, setBitcoinConnect] = useState(false);
|
||||||
const [zaps, setZaps] = useState([]);
|
|
||||||
const [zapAmount, setZapAmount] = useState(0);
|
const [zapAmount, setZapAmount] = useState(0);
|
||||||
|
|
||||||
const { fetchZapsForEvent } = useNostr();
|
const { zaps, zapsLoading, zapsError } = useZapsQuery({ event: lesson, type: "lesson" });
|
||||||
const { returnImageProxy } = useImageProxy();
|
const { returnImageProxy } = useImageProxy();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -46,29 +43,22 @@ const CourseLesson = ({ lesson, course }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!zaps || zaps.length === 0) return;
|
if (!zaps || zapsLoading || zapsError) return;
|
||||||
|
|
||||||
let total = 0;
|
let total = 0;
|
||||||
zaps.forEach((zap) => {
|
zaps.forEach((zap) => {
|
||||||
|
if (zap.tags.find(tag => tag[0] === "e" && tag[1] === lesson.id) || zap.tags.find(tag => tag[0] === "a" && tag[1] === `${lesson.kind}:${lesson.id}:${lesson.d}`)) {
|
||||||
const bolt11Tag = zap.tags.find(tag => tag[0] === "bolt11");
|
const bolt11Tag = zap.tags.find(tag => tag[0] === "bolt11");
|
||||||
const invoice = bolt11Tag ? bolt11Tag[1] : null;
|
const invoice = bolt11Tag ? bolt11Tag[1] : null;
|
||||||
if (invoice) {
|
if (invoice) {
|
||||||
const amount = getSatAmountFromInvoice(invoice);
|
const amount = getSatAmountFromInvoice(invoice);
|
||||||
total += amount;
|
total += amount;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
setZapAmount(total);
|
setZapAmount(total);
|
||||||
}, [zaps]);
|
}, [zaps, zapsLoading, zapsError]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchZaps = async () => {
|
|
||||||
if (lesson) {
|
|
||||||
const zaps = await fetchZapsForEvent(lesson);
|
|
||||||
setZaps(zaps);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fetchZaps();
|
|
||||||
}, [fetchZapsForEvent, lesson]);
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full px-24 pt-12 mx-auto mt-4 max-tab:px-0 max-mob:px-0 max-tab:pt-2 max-mob:pt-2'>
|
<div className='w-full px-24 pt-12 mx-auto mt-4 max-tab:px-0 max-mob:px-0 max-tab:pt-2 max-mob:pt-2'>
|
||||||
<div className='w-full flex flex-row justify-between max-tab:flex-col max-mob:flex-col'>
|
<div className='w-full flex flex-row justify-between max-tab:flex-col max-mob:flex-col'>
|
||||||
@ -79,8 +69,7 @@ const CourseLesson = ({ lesson, course }) => {
|
|||||||
lesson.topics.map((topic, index) => (
|
lesson.topics.map((topic, index) => (
|
||||||
<Tag className='mr-2 text-white' key={index} value={topic}></Tag>
|
<Tag className='mr-2 text-white' key={index} value={topic}></Tag>
|
||||||
))
|
))
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<h1 className='text-4xl mt-6'>{lesson?.title}</h1>
|
<h1 className='text-4xl mt-6'>{lesson?.title}</h1>
|
||||||
<p className='text-xl mt-6'>{lesson?.summary}</p>
|
<p className='text-xl mt-6'>{lesson?.summary}</p>
|
||||||
@ -95,7 +84,7 @@ const CourseLesson = ({ lesson, course }) => {
|
|||||||
<p className='text-lg'>
|
<p className='text-lg'>
|
||||||
Created by{' '}
|
Created by{' '}
|
||||||
<a rel='noreferrer noopener' target='_blank' className='text-blue-500 hover:underline'>
|
<a rel='noreferrer noopener' target='_blank' className='text-blue-500 hover:underline'>
|
||||||
{lesson.author?.username}
|
{lesson.author?.username || lesson.author?.name || lesson.author?.pubkey}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -116,7 +105,7 @@ const CourseLesson = ({ lesson, course }) => {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full flex justify-end">
|
<div className="w-full flex justify-end">
|
||||||
<ZapDisplay zapAmount={zapAmount} event={parseEvent(course)} />
|
<ZapDisplay zapAmount={zapAmount} event={lesson} zapsLoading={zapsLoading} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -20,9 +20,6 @@ export function useZapsQuery({ event, type }) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const events = await ndk.fetchEvents(filters, { closeOnEose: true });
|
const events = await ndk.fetchEvents(filters, { closeOnEose: true });
|
||||||
if (event.id === "f679183f0e66878142186cf7d0ae44ab137dabed1dfcb28a609472afbb7c8d51") {
|
|
||||||
console.log('events', events);
|
|
||||||
}
|
|
||||||
return events;
|
return events;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching zaps from NDK:', error);
|
console.error('Error fetching zaps from NDK:', error);
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState, useCallback } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useNostr } from "@/hooks/useNostr";
|
|
||||||
import { parseCourseEvent, parseEvent, findKind0Fields } from "@/utils/nostr";
|
import { parseCourseEvent, parseEvent, findKind0Fields } from "@/utils/nostr";
|
||||||
import CourseDetails from "@/components/course/CourseDetails";
|
import CourseDetails from "@/components/course/CourseDetails";
|
||||||
import CourseLesson from "@/components/course/CourseLesson";
|
import CourseLesson from "@/components/course/CourseLesson";
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
|
import { useNDKContext } from "@/context/NDKContext";
|
||||||
|
|
||||||
const MDDisplay = dynamic(
|
const MDDisplay = dynamic(
|
||||||
() => import("@uiw/react-markdown-preview"),
|
() => import("@uiw/react-markdown-preview"),
|
||||||
{
|
{
|
||||||
@ -18,58 +19,81 @@ const Course = () => {
|
|||||||
const [lessons, setLessons] = useState([]);
|
const [lessons, setLessons] = useState([]);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { fetchSingleEvent, fetchSingleNaddrEvent, fetchKind0 } = useNostr();
|
const ndk = useNDKContext();
|
||||||
|
|
||||||
const { slug } = router.query;
|
const fetchAuthor = useCallback(async (pubkey) => {
|
||||||
|
const author = await ndk.getUser({ pubkey });
|
||||||
const fetchAuthor = async (pubkey) => {
|
const profile = await author.fetchProfile();
|
||||||
const author = await fetchKind0(pubkey);
|
const fields = await findKind0Fields(profile);
|
||||||
const fields = await findKind0Fields(author);
|
|
||||||
if (fields) {
|
if (fields) {
|
||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
}
|
}, [ndk]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getCourse = async () => {
|
if (router.isReady) {
|
||||||
if (slug) {
|
const { slug } = router.query;
|
||||||
const fetchedCourse = await fetchSingleEvent(slug);
|
|
||||||
const formattedCourse = parseCourseEvent(fetchedCourse);
|
const fetchCourse = async (slug) => {
|
||||||
const aTags = formattedCourse.tags.filter(tag => tag[0] === 'a');
|
try {
|
||||||
setCourse(formattedCourse);
|
await ndk.connect();
|
||||||
if (aTags.length > 0) {
|
|
||||||
const lessonIds = aTags.map(tag => tag[1]);
|
const filter = {
|
||||||
setLessonIds(lessonIds);
|
ids: [slug]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const event = await ndk.fetchEvent(filter);
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
const author = await fetchAuthor(event.pubkey);
|
||||||
|
const aTags = event.tags.filter(tag => tag[0] === 'a');
|
||||||
|
const lessonIds = aTags.map(tag => tag[1].split(':')[2]);
|
||||||
|
setLessonIds(lessonIds);
|
||||||
|
const parsedCourse = {
|
||||||
|
...parseCourseEvent(event),
|
||||||
|
author
|
||||||
|
};
|
||||||
|
setCourse(parsedCourse);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching event:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if (ndk) {
|
||||||
if (slug && !course) {
|
fetchCourse(slug);
|
||||||
getCourse();
|
|
||||||
}
|
}
|
||||||
}, [slug]);
|
}
|
||||||
|
}, [router.isReady, router.query, ndk, fetchAuthor]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lessonIds.length > 0) {
|
if (lessonIds.length > 0) {
|
||||||
|
|
||||||
const fetchLesson = async (lessonId) => {
|
const fetchLesson = async (lessonId) => {
|
||||||
try {
|
try {
|
||||||
const l = await fetchSingleNaddrEvent(lessonId.split(':')[2]);
|
await ndk.connect();
|
||||||
const author = await fetchAuthor(l.pubkey);
|
|
||||||
const parsedLesson = parseEvent(l);
|
const filter = {
|
||||||
const lessonObj = {
|
"#d": [lessonId]
|
||||||
...parsedLesson,
|
}
|
||||||
|
|
||||||
|
const event = await ndk.fetchEvent(filter);
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
const author = await fetchAuthor(event.pubkey);
|
||||||
|
const parsedLesson = {
|
||||||
|
...parseEvent(event),
|
||||||
author
|
author
|
||||||
|
};
|
||||||
|
setLessons(prev => [...prev, parsedLesson]);
|
||||||
}
|
}
|
||||||
setLessons(prev => [...prev, lessonObj]);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching lesson:', error);
|
console.error('Error fetching event:', error);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
lessonIds.forEach(lessonId => fetchLesson(lessonId));
|
lessonIds.forEach(lessonId => fetchLesson(lessonId));
|
||||||
}
|
}
|
||||||
}, [lessonIds]);
|
}, [lessonIds, ndk, fetchAuthor]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -38,7 +38,6 @@ export default function Details() {
|
|||||||
const [zapAmount, setZapAmount] = useState(null);
|
const [zapAmount, setZapAmount] = useState(null);
|
||||||
const [paidResource, setPaidResource] = useState(false);
|
const [paidResource, setPaidResource] = useState(false);
|
||||||
const [decryptedContent, setDecryptedContent] = useState(null);
|
const [decryptedContent, setDecryptedContent] = useState(null);
|
||||||
// const [user, setUser] = useState(null);
|
|
||||||
|
|
||||||
const ndk = useNDKContext();
|
const ndk = useNDKContext();
|
||||||
const [user] = useLocalStorageWithEffect('user', {});
|
const [user] = useLocalStorageWithEffect('user', {});
|
||||||
@ -78,7 +77,7 @@ export default function Details() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
decryptContent();
|
decryptContent();
|
||||||
}, [user, paidResource]);
|
}, [user, paidResource, processedEvent]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (router.isReady) {
|
if (router.isReady) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user