Cleaning up course display and zap displays

This commit is contained in:
austinkelsay 2024-08-01 17:10:43 -05:00
parent 8d4c9aaa83
commit 1a72532a4f
11 changed files with 219 additions and 169 deletions

View File

@ -71,7 +71,11 @@ export default function CoursesCarousel() {
<Carousel
value={!processedCourses.length > 0 ? [{}, {}, {}] : [...processedCourses]}
numVisible={2}
itemTemplate={!processedCourses.length > 0 ? TemplateSkeleton : CourseTemplate}
itemTemplate={(item) =>
processedCourses.length > 0 ?
<CourseTemplate key={item.id} course={item} /> :
<TemplateSkeleton key={Math.random()} />
}
responsiveOptions={responsiveOptions} />
</div>
</>

View File

@ -66,7 +66,11 @@ export default function ResourcesCarousel() {
<h2 className="ml-[6%] mt-4">Resources</h2>
<Carousel value={!processedResources.length > 0 ? [{}, {}, {}] : [...processedResources]}
numVisible={2}
itemTemplate={!processedResources.length > 0 ? TemplateSkeleton : ResourceTemplate}
itemTemplate={(item) =>
processedResources.length > 0 ?
<ResourceTemplate key={item.id} resource={item} /> :
<TemplateSkeleton key={Math.random()} />
}
responsiveOptions={responsiveOptions} />
</>
);

View File

@ -68,7 +68,11 @@ export default function WorkshopsCarousel() {
<h2 className="ml-[6%] mt-4">Workshops</h2>
<Carousel value={!processedWorkshops.length > 0 ? [{}, {}, {}] : [...processedWorkshops]}
numVisible={2}
itemTemplate={!processedWorkshops.length > 0 ? TemplateSkeleton : WorkshopTemplate}
itemTemplate={(item) =>
processedWorkshops.length > 0 ?
<WorkshopTemplate key={item.id} workshop={item} /> :
<TemplateSkeleton key={Math.random()} />
}
responsiveOptions={responsiveOptions} />
</>
);

View File

@ -7,7 +7,7 @@ import { useNostr } from "@/hooks/useNostr";
import { getSatAmountFromInvoice } from "@/utils/lightning";
import ZapDisplay from "@/components/zaps/ZapDisplay";
const CourseTemplate = (course) => {
const CourseTemplate = ({course}) => {
const [zapAmount, setZapAmount] = useState(null);
const router = useRouter();
const { returnImageProxy } = useImageProxy();

View File

@ -6,7 +6,7 @@ import { useImageProxy } from "@/hooks/useImageProxy";
import { useNostr } from "@/hooks/useNostr";
import { getSatAmountFromInvoice } from "@/utils/lightning";
const ResourceTemplate = (resource) => {
const ResourceTemplate = ({resource}) => {
const [zapAmount, setZapAmount] = useState(null);
const router = useRouter();
const { returnImageProxy } = useImageProxy();

View File

@ -6,7 +6,7 @@ import { useImageProxy } from "@/hooks/useImageProxy";
import { useNostr } from "@/hooks/useNostr";
import { getSatAmountFromInvoice } from "@/utils/lightning";
const WorkshopTemplate = (workshop) => {
const WorkshopTemplate = ({workshop}) => {
const [zapAmount, setZapAmount] = useState(null);
const router = useRouter();
const { returnImageProxy } = useImageProxy();

View File

@ -5,6 +5,7 @@ import { useNostr } from '@/hooks/useNostr';
import { findKind0Fields } from '@/utils/nostr';
import { useImageProxy } from '@/hooks/useImageProxy';
import ZapDisplay from '@/components/zaps/ZapDisplay';
import { getSatAmountFromInvoice } from '@/utils/lightning';
import { Tag } from 'primereact/tag';
import { nip19 } from 'nostr-tools';
import { useLocalStorageWithEffect } from '@/hooks/useLocalStorage';
@ -31,9 +32,10 @@ export default function CourseDetails({processedEvent}) {
const [bitcoinConnect, setBitcoinConnect] = useState(false);
const [nAddress, setNAddress] = useState(null);
const [user] = useLocalStorageWithEffect('user', {});
const [zaps, setZaps] = useState([]);
const [zapAmount, setZapAmount] = useState(0);
const { returnImageProxy } = useImageProxy();
const { fetchKind0, zapEvent } = useNostr();
const { fetchKind0, zapEvent, fetchZapsForEvent } = useNostr();
const router = useRouter();
@ -80,6 +82,31 @@ export default function CourseDetails({processedEvent}) {
}
}, [processedEvent]);
useEffect(() => {
if (!zaps || zaps.length === 0) return;
let total = 0;
zaps.forEach((zap) => {
const bolt11Tag = zap.tags.find(tag => tag[0] === "bolt11");
const invoice = bolt11Tag ? bolt11Tag[1] : null;
if (invoice) {
const amount = getSatAmountFromInvoice(invoice);
total += amount;
}
});
setZapAmount(total);
}, [zaps]);
useEffect(() => {
const fetchZaps = async () => {
if (processedEvent) {
const zaps = await fetchZapsForEvent(processedEvent);
setZaps(zaps);
}
}
fetchZaps();
}, [fetchZapsForEvent, processedEvent]);
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 flex flex-row justify-between max-tab:flex-col max-mob:flex-col'>

View File

@ -0,0 +1,136 @@
import React, { useEffect, useState } from "react";
import { Tag } from "primereact/tag";
import Image from "next/image";
import { useImageProxy } from "@/hooks/useImageProxy";
import { useNostr } from "@/hooks/useNostr";
import { getSatAmountFromInvoice } from "@/utils/lightning";
import { parseEvent } from "@/utils/nostr";
import { useLocalStorageWithEffect } from "@/hooks/useLocalStorage";
import ZapDisplay from "@/components/zaps/ZapDisplay";
import dynamic from "next/dynamic";
const BitcoinConnectPayButton = dynamic(
() => import('@getalby/bitcoin-connect-react').then((mod) => mod.PayButton),
{
ssr: false,
}
);
const MDDisplay = dynamic(
() => import("@uiw/react-markdown-preview"),
{
ssr: false,
}
);
const CourseLesson = ({ lesson, course }) => {
const [bitcoinConnect, setBitcoinConnect] = useState(false);
const [zaps, setZaps] = useState([]);
const [zapAmount, setZapAmount] = useState(0);
const { fetchZapsForEvent } = useNostr();
const { returnImageProxy } = useImageProxy();
useEffect(() => {
if (typeof window === 'undefined') return;
const bitcoinConnectConfig = window.localStorage.getItem('bc:config');
if (bitcoinConnectConfig) {
setBitcoinConnect(true);
}
}, []);
const handleZapEvent = async () => {
return;
}
useEffect(() => {
if (!zaps || zaps.length === 0) return;
let total = 0;
zaps.forEach((zap) => {
const bolt11Tag = zap.tags.find(tag => tag[0] === "bolt11");
const invoice = bolt11Tag ? bolt11Tag[1] : null;
if (invoice) {
const amount = getSatAmountFromInvoice(invoice);
total += amount;
}
});
setZapAmount(total);
}, [zaps]);
useEffect(() => {
const fetchZaps = async () => {
if (lesson) {
const zaps = await fetchZapsForEvent(lesson);
setZaps(zaps);
}
}
fetchZaps();
}, [fetchZapsForEvent, lesson]);
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 flex flex-row justify-between max-tab:flex-col max-mob:flex-col'>
<div className='w-[75vw] mx-auto flex flex-row items-start justify-between max-tab:flex-col max-mob:flex-col max-tab:w-[95vw] max-mob:w-[95vw]'>
<div className='flex flex-col items-start max-w-[45vw] max-tab:max-w-[100vw] max-mob:max-w-[100vw]'>
<div className='pt-2 flex flex-row justify-start w-full'>
{lesson && lesson.topics && lesson.topics.length > 0 && (
lesson.topics.map((topic, index) => (
<Tag className='mr-2 text-white' key={index} value={topic}></Tag>
))
)
}
</div>
<h1 className='text-4xl mt-6'>{lesson?.title}</h1>
<p className='text-xl mt-6'>{lesson?.summary}</p>
<div className='flex flex-row w-full mt-6 items-center'>
<Image
alt="avatar thumbnail"
src={returnImageProxy(lesson.author?.avatar, lesson.author?.pubkey)}
width={50}
height={50}
className="rounded-full mr-4"
/>
<p className='text-lg'>
Created by{' '}
<a rel='noreferrer noopener' target='_blank' className='text-blue-500 hover:underline'>
{lesson.author?.username}
</a>
</p>
</div>
</div>
<div className='flex flex-col max-tab:mt-12 max-mob:mt-12'>
{lesson && (
<div className='flex flex-col items-center justify-between rounded-lg h-72 p-4 bg-gray-700 drop-shadow-md'>
<Image
alt="resource thumbnail"
src={returnImageProxy(lesson.image)}
width={344}
height={194}
className="w-[344px] h-[194px] object-cover object-top rounded-lg"
/>
{bitcoinConnect ? (
<div>
<BitcoinConnectPayButton onClick={handleZapEvent} />
</div>
) : (
<div className="w-full flex justify-end">
<ZapDisplay zapAmount={zapAmount} event={parseEvent(course)} />
</div>
)}
</div>
)}
</div>
</div>
</div>
<div className='w-[75vw] mx-auto mt-12 p-12 border-t-2 border-gray-300 max-tab:p-0 max-mob:p-0 max-tab:max-w-[100vw] max-mob:max-w-[100vw]'>
{
lesson?.content && <MDDisplay source={lesson.content} />
}
</div>
</div>
)
}
export default CourseLesson;

View File

@ -24,9 +24,6 @@ export function useNostr() {
const lastSubscriptionTime = useRef(0);
const throttleDelay = 2000;
// ref to keep track of active subscriptions
const activeSubscriptions = useRef([]);
const processSubscriptionQueue = useCallback(() => {
if (subscriptionQueue.current.length === 0) return;
@ -48,23 +45,7 @@ export function useNostr() {
if (!pool) return;
const subscriptionFn = () => {
// Create the subscription
const sub = pool.subscribeMany(defaultRelays, filters, {
...opts,
oneose: () => {
// Call the original oneose if it exists
opts.oneose?.();
// Close the subscription after EOSE
sub.close();
// Remove this subscription from activeSubscriptions
activeSubscriptions.current = activeSubscriptions.current.filter(s => s !== sub);
}
});
// Add this subscription to activeSubscriptions
activeSubscriptions.current.push(sub);
return sub;
return pool.subscribeMany(defaultRelays, filters, opts);
};
subscriptionQueue.current.push(subscriptionFn);
@ -73,19 +54,6 @@ export function useNostr() {
[pool, processSubscriptionQueue]
);
// Add this new function to close all active subscriptions
const closeAllSubscriptions = useCallback(() => {
activeSubscriptions.current.forEach(sub => sub.close());
activeSubscriptions.current = [];
}, []);
// Use an effect to close all subscriptions when the component unmounts
useEffect(() => {
return () => {
closeAllSubscriptions();
};
}, [closeAllSubscriptions]);
const publish = useCallback(
async (event) => {
if (!pool) return;

View File

@ -2,12 +2,8 @@ import React, { useEffect, useState } from "react";
import { useRouter } from "next/router";
import { useNostr } from "@/hooks/useNostr";
import { parseCourseEvent, parseEvent, findKind0Fields } from "@/utils/nostr";
import { useImageProxy } from "@/hooks/useImageProxy";
import { Button } from "primereact/button";
import { Tag } from "primereact/tag";
import Image from "next/image";
import CourseDetails from "@/components/CourseDetails";
import { nip19 } from "nostr-tools";
import CourseDetails from "@/components/course/CourseDetails";
import CourseLesson from "@/components/course/CourseLesson";
import dynamic from 'next/dynamic';
const MDDisplay = dynamic(
() => import("@uiw/react-markdown-preview"),
@ -15,22 +11,14 @@ const MDDisplay = dynamic(
ssr: false,
}
);
const BitcoinConnectPayButton = dynamic(
() => import('@getalby/bitcoin-connect-react').then((mod) => mod.PayButton),
{
ssr: false,
}
);
const Course = () => {
const [course, setCourse] = useState(null);
const [lessonIds, setLessonIds] = useState([]);
const [lessons, setLessons] = useState([]);
const [bitcoinConnect, setBitcoinConnect] = useState(false);
const router = useRouter();
const { fetchSingleEvent, fetchSingleNaddrEvent, fetchKind0 } = useNostr();
const { returnImageProxy } = useImageProxy();
const { slug } = router.query;
@ -42,24 +30,6 @@ const Course = () => {
}
}
const handleZapEvent = async () => {
if (!event) return;
const response = await zapEvent(event);
console.log('zap response:', response);
}
useEffect(() => {
if (typeof window === 'undefined') return;
const bitcoinConnectConfig = window.localStorage.getItem('bc:config');
if (bitcoinConnectConfig) {
setBitcoinConnect(true);
}
}, []);
useEffect(() => {
const getCourse = async () => {
if (slug) {
@ -70,7 +40,6 @@ const Course = () => {
if (aTags.length > 0) {
const lessonIds = aTags.map(tag => tag[1]);
setLessonIds(lessonIds);
console.log("LESSON IDS", lessonIds);
}
}
};
@ -102,89 +71,12 @@ const Course = () => {
}
}, [lessonIds]);
useEffect(() => {
console.log("AHHHHH", lessons);
}, [lessons])
return (
<>
<CourseDetails processedEvent={course} />
{lessons.length > 0 && lessons.map((lesson, index) => (
<div key={index} 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-[75vw] mx-auto flex flex-row items-start justify-between max-tab:flex-col max-mob:flex-col max-tab:w-[95vw] max-mob:w-[95vw]'>
<div className='flex flex-col items-start max-w-[45vw] max-tab:max-w-[100vw] max-mob:max-w-[100vw]'>
<div className='pt-2 flex flex-row justify-start w-full'>
{lesson && lesson.topics && lesson.topics.length > 0 && (
lesson.topics.map((topic, index) => (
<Tag className='mr-2 text-white' key={index} value={topic}></Tag>
))
)
}
</div>
<h1 className='text-4xl mt-6'>{lesson?.title}</h1>
<p className='text-xl mt-6'>{lesson?.summary}</p>
<div className='flex flex-row w-full mt-6 items-center'>
<Image
alt="avatar thumbnail"
src={returnImageProxy(lesson.author?.avatar, lesson.author?.pubkey)}
width={50}
height={50}
className="rounded-full mr-4"
/>
<p className='text-lg'>
Created by{' '}
<a rel='noreferrer noopener' target='_blank' className='text-blue-500 hover:underline'>
{lesson.author?.username}
</a>
</p>
</div>
</div>
<div className='flex flex-col max-tab:mt-12 max-mob:mt-12'>
{lesson && (
<div className='flex flex-col items-center justify-between rounded-lg h-72 p-4 bg-gray-700 drop-shadow-md'>
<Image
alt="resource thumbnail"
src={returnImageProxy(lesson.image)}
width={344}
height={194}
className="w-[344px] h-[194px] object-cover object-top rounded-lg"
/>
{bitcoinConnect ? (
<div>
<BitcoinConnectPayButton onClick={handleZapEvent} />
</div>
) : (
<div>
<Button
icon="pi pi-bolt"
label="Zap"
severity="success"
outlined
onClick={handleZapEvent}
pt={{
button: {
icon: ({ context }) => ({
className: 'bg-yellow-500'
})
}
}}
/>
</div>
)}
</div>
)}
</div>
</div>
</div>
<div className='w-[75vw] mx-auto mt-12 p-12 border-t-2 border-gray-300 max-tab:p-0 max-mob:p-0 max-tab:max-w-[100vw] max-mob:max-w-[100vw]'>
{
lesson?.content && <MDDisplay source={lesson.content} />
}
</div>
</div>
))
}
<CourseLesson key={index} lesson={lesson} course={course} />
))}
<div className="mx-auto my-6">
{
course?.content && <MDDisplay source={course.content} />

View File

@ -4,7 +4,8 @@ import { useRouter } from 'next/router';
import { useNostr } from '@/hooks/useNostr';
import { parseEvent, findKind0Fields, hexToNpub } from '@/utils/nostr';
import { useImageProxy } from '@/hooks/useImageProxy';
import { Button } from 'primereact/button';
import { getSatAmountFromInvoice } from '@/utils/lightning';
import ZapDisplay from '@/components/zaps/ZapDisplay';
import { Tag } from 'primereact/tag';
import { nip19 } from 'nostr-tools';
import { useLocalStorageWithEffect } from '@/hooks/useLocalStorage';
@ -32,10 +33,12 @@ export default function Details() {
const [author, setAuthor] = useState(null);
const [bitcoinConnect, setBitcoinConnect] = useState(false);
const [nAddress, setNAddress] = useState(null);
const [zaps, setZaps] = useState([]);
const [zapAmount, setZapAmount] = useState(0);
const [user] = useLocalStorageWithEffect('user', {});
console.log('user:', user);
const { returnImageProxy } = useImageProxy();
const { fetchSingleEvent, fetchKind0, zapEvent } = useNostr();
const { fetchSingleEvent, fetchKind0, zapEvent, fetchZapsForEvent } = useNostr();
const router = useRouter();
@ -105,6 +108,31 @@ export default function Details() {
}
}, [processedEvent]);
useEffect(() => {
if (!zaps || zaps.length === 0) return;
let total = 0;
zaps.forEach((zap) => {
const bolt11Tag = zap.tags.find(tag => tag[0] === "bolt11");
const invoice = bolt11Tag ? bolt11Tag[1] : null;
if (invoice) {
const amount = getSatAmountFromInvoice(invoice);
total += amount;
}
});
setZapAmount(total);
}, [zaps]);
useEffect(() => {
const fetchZaps = async () => {
if (event) {
const zaps = await fetchZapsForEvent(event);
setZaps(zaps);
}
}
fetchZaps();
}, [fetchZapsForEvent, event]);
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 flex flex-row justify-between max-tab:flex-col max-mob:flex-col'>
@ -152,21 +180,8 @@ export default function Details() {
<BitcoinConnectPayButton onClick={handleZapEvent} />
</div>
) : (
<div>
<Button
icon="pi pi-bolt"
label="Zap"
severity="success"
outlined
onClick={handleZapEvent}
pt={{
button: {
icon: ({ context }) => ({
className: 'bg-yellow-500'
})
}
}}
/>
<div className="w-full flex justify-end">
<ZapDisplay zapAmount={zapAmount} event={parseEvent(event)} />
</div>
)}
</div>