From eaad9776778c9b0aada9aa128400d104110a89ab Mon Sep 17 00:00:00 2001 From: austinkelsay Date: Sun, 4 Aug 2024 18:00:59 -0500 Subject: [PATCH] Zaps are implemented but still fickle, added to details page --- .../content/carousels/CoursesCarousel.js | 22 +---- .../content/carousels/ResourcesCarousel.js | 22 +---- .../content/carousels/WorkshopsCarousel.js | 23 +---- .../carousels/templates/CourseTemplate.js | 20 ++-- .../carousels/templates/ResourceTemplate.js | 11 ++- .../carousels/templates/WorkshopTemplate.js | 4 +- src/context/NDKContext.js | 8 -- ...useZapsQuery.js => useCoursesZapsQuery.js} | 38 ++++---- .../nostrQueries/useResourceZapsQuery.js | 51 ++++++++++ .../nostrQueries/useWorkshopsZapsQuery.js | 51 ++++++++++ src/pages/details/[slug].js | 94 ++++++++++++------- src/pages/index.js | 4 +- 12 files changed, 215 insertions(+), 133 deletions(-) rename src/hooks/nostrQueries/{useZapsQuery.js => useCoursesZapsQuery.js} (52%) create mode 100644 src/hooks/nostrQueries/useResourceZapsQuery.js create mode 100644 src/hooks/nostrQueries/useWorkshopsZapsQuery.js diff --git a/src/components/content/carousels/CoursesCarousel.js b/src/components/content/carousels/CoursesCarousel.js index 614ee4b..5cdbfb4 100644 --- a/src/components/content/carousels/CoursesCarousel.js +++ b/src/components/content/carousels/CoursesCarousel.js @@ -1,7 +1,6 @@ import React, { useState, useEffect, use } from 'react'; import { Carousel } from 'primereact/carousel'; import { parseCourseEvent } from '@/utils/nostr'; -import { useZapsQuery } from '@/hooks/nostrQueries/useZapsQuery'; import CourseTemplate from '@/components/content/carousels/templates/CourseTemplate'; import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton'; import { useCoursesQuery } from '@/hooks/nostrQueries/useCoursesQuery'; @@ -27,29 +26,14 @@ const responsiveOptions = [ export default function CoursesCarousel() { const [processedCourses, setProcessedCourses] = useState([]); const { courses, coursesLoading, coursesError, refetchCourses } = useCoursesQuery() - const { zaps, zapsLoading, zapsError, refetchZaps } = useZapsQuery({ events: courses }) - - useEffect(() => { - refetchZaps(courses) - }, [courses, refetchZaps]); useEffect(() => { const fetch = async () => { try { - if (courses && courses.length > 0 && zaps) { + if (courses && courses.length > 0) { const processedCourses = courses.map(course => parseCourseEvent(course)); - let coursesWithZaps = processedCourses.map(course => { - let collectedZaps = [] - zaps.forEach(zap => { - if (zap.tags.find(tag => tag[0] === "e" && tag[1] === course.id) || zap.tags.find(tag => tag[0] === "a" && tag[1] === `${course.kind}:${course.id}:${course.d}`)) { - collectedZaps.push(zap) - } - }) - return { ...course, zaps: collectedZaps } - }) - - setProcessedCourses(coursesWithZaps); + setProcessedCourses(processedCourses); } else { console.log('No courses fetched or empty array returned'); } @@ -58,7 +42,7 @@ export default function CoursesCarousel() { } }; fetch(); - }, [courses, zaps]); + }, [courses]); if (coursesError) { return
Error: {coursesError.message}
diff --git a/src/components/content/carousels/ResourcesCarousel.js b/src/components/content/carousels/ResourcesCarousel.js index 21d586d..d941a2b 100644 --- a/src/components/content/carousels/ResourcesCarousel.js +++ b/src/components/content/carousels/ResourcesCarousel.js @@ -4,7 +4,6 @@ import { parseEvent } from '@/utils/nostr'; import ResourceTemplate from '@/components/content/carousels/templates/ResourceTemplate'; import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton'; import { useResourcesQuery } from '@/hooks/nostrQueries/useResourcesQuery'; -import { useZapsQuery } from '@/hooks/nostrQueries/useZapsQuery'; const responsiveOptions = [ { @@ -27,31 +26,14 @@ const responsiveOptions = [ export default function ResourcesCarousel() { const [processedResources, setProcessedResources] = useState([]); const { resources, resourcesLoading, resourcesError, refetchResources } = useResourcesQuery() - const { zaps, zapsLoading, zapsError, refetchZaps } = useZapsQuery({ events: resources }) - - useEffect(() => { - if (resources && resources.length > 0) { - refetchZaps(resources) - } - }, [resources, refetchZaps]); useEffect(() => { const fetch = async () => { try { - if (resources && resources.length > 0 && zaps) { + if (resources && resources.length > 0) { const processedResources = resources.map(resource => parseEvent(resource)); - let resourcesWithZaps = processedResources.map(resource => { - let collectedZaps = [] - zaps.forEach(zap => { - if (zap.tags.find(tag => tag[0] === "e" && tag[1] === resource.id) || zap.tags.find(tag => tag[0] === "a" && tag[1] === `${resource.kind}:${resource.id}:${resource.d}`)) { - collectedZaps.push(zap) - } - }) - return { ...resource, zaps: collectedZaps } - }) - - setProcessedResources(resourcesWithZaps); + setProcessedResources(processedResources); } } catch (error) { console.error('Error fetching resources:', error); diff --git a/src/components/content/carousels/WorkshopsCarousel.js b/src/components/content/carousels/WorkshopsCarousel.js index 16f0027..b9e65a0 100644 --- a/src/components/content/carousels/WorkshopsCarousel.js +++ b/src/components/content/carousels/WorkshopsCarousel.js @@ -4,7 +4,6 @@ import { parseEvent } from '@/utils/nostr'; import WorkshopTemplate from '@/components/content/carousels/templates/WorkshopTemplate'; import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton'; import { useWorkshopsQuery } from '@/hooks/nostrQueries/useWorkshopsQuery'; -import { useZapsQuery } from '@/hooks/nostrQueries/useZapsQuery'; const responsiveOptions = [ { @@ -27,30 +26,14 @@ const responsiveOptions = [ export default function WorkshopsCarousel() { const [processedWorkshops, setProcessedWorkshops] = useState([]) const { workshops, workshopsLoading, workshopsError, refetchWorkshops } = useWorkshopsQuery() - const { zaps, zapsLoading, zapsError, refetchZaps } = useZapsQuery({ events: workshops }) - - useEffect(() => { - refetchZaps(workshops) - }, [workshops, refetchZaps]); useEffect(() => { const fetch = async () => { try { - console.debug('workshops', workshops); - if (workshops && workshops.length > 0 && zaps) { + if (workshops && workshops.length > 0) { const processedWorkshops = workshops.map(workshop => parseEvent(workshop)); - let workshopsWithZaps = processedWorkshops.map(workshop => { - let collectedZaps = [] - zaps.forEach(zap => { - if (zap.tags.find(tag => tag[0] === "e" && tag[1] === workshop.id) || zap.tags.find(tag => tag[0] === "a" && tag[1] === `${workshop.kind}:${workshop.id}:${workshop.d}`)) { - collectedZaps.push(zap) - } - }) - return { ...workshop, zaps: collectedZaps } - }) - - setProcessedWorkshops(workshopsWithZaps); + setProcessedWorkshops(processedWorkshops); } else { console.log('No workshops fetched or empty array returned'); } @@ -59,7 +42,7 @@ export default function WorkshopsCarousel() { } }; fetch(); - }, [workshops, zaps]); + }, [workshops]); if (workshopsLoading) return
Loading...
; if (workshopsError) return
Error: {workshopsError}
; diff --git a/src/components/content/carousels/templates/CourseTemplate.js b/src/components/content/carousels/templates/CourseTemplate.js index 0f2ea70..972fe11 100644 --- a/src/components/content/carousels/templates/CourseTemplate.js +++ b/src/components/content/carousels/templates/CourseTemplate.js @@ -5,27 +5,31 @@ import { formatTimestampToHowLongAgo } from "@/utils/time"; import { useImageProxy } from "@/hooks/useImageProxy"; import { getSatAmountFromInvoice } from "@/utils/lightning"; import ZapDisplay from "@/components/zaps/ZapDisplay"; +import { useCoursesZapsQuery } from "@/hooks/nostrQueries/useCoursesZapsQuery"; const CourseTemplate = ({ course }) => { const [zapAmount, setZapAmount] = useState(0); const router = useRouter(); const { returnImageProxy } = useImageProxy(); + const { zaps, zapsLoading, zapsError, refetchZaps } = useCoursesZapsQuery({ event: course }) useEffect(() => { - if (!course?.zaps || !course?.zaps.length > 0) return; + if (!zaps || !zaps.length > 0) return; let total = 0; - course.zaps.forEach((zap) => { + zaps.forEach((zap) => { // If the zap matches the event or the parameterized event, then add the zap to the total - const bolt11Tag = zap.tags.find(tag => tag[0] === "bolt11"); - const invoice = bolt11Tag ? bolt11Tag[1] : null; - if (invoice) { - const amount = getSatAmountFromInvoice(invoice); - total += amount; + if (zap.tags.find(tag => tag[0] === "e" && tag[1] === course.id) || zap.tags.find(tag => tag[0] === "a" && tag[1] === `${course.kind}:${course.id}:${course.d}`)) { + 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); - }, [course]); + }, [course, zaps]); return (
{ const [zapAmount, setZapAmount] = useState(0); + const { zaps, zapsLoading, zapsError, refetchZaps } = useResourceZapsQuery({ event: resource }) const router = useRouter(); const { returnImageProxy } = useImageProxy(); useEffect(() => { - if (!resource?.zaps || !resource?.zaps.length > 0) return; + if (!zaps || !zaps.length > 0) return; let total = 0; - resource.zaps.forEach((zap) => { + zaps.forEach((zap) => { // If the zap matches the event or the parameterized event, then add the zap to the total + if (zap.tags.find(tag => tag[0] === "e" && tag[1] === resource.id) || zap.tags.find(tag => tag[0] === "a" && tag[1] === `${resource.kind}:${resource.id}:${resource.d}`)) { 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); - }, [resource]); + }, [resource, zaps]); return (
{ const [zapAmount, setZapAmount] = useState(0); const router = useRouter(); const { returnImageProxy } = useImageProxy(); - const { zaps, zapsLoading, zapsError, refetchZaps } = useZapsQuery({event: workshop}); + const { zaps, zapsLoading, zapsError, refetchZaps } = useWorkshopsZapsQuery({event: workshop}); useEffect(() => { if (!zaps || !zaps.length > 0) return; diff --git a/src/context/NDKContext.js b/src/context/NDKContext.js index c66ec3f..53cbfda 100644 --- a/src/context/NDKContext.js +++ b/src/context/NDKContext.js @@ -3,14 +3,6 @@ import NDK from "@nostr-dev-kit/ndk"; const NDKContext = createContext(null); -// Define the public key of the author whose events we want to fetch. -const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY; - -// Ensure AUTHOR_PUBKEY is defined -if (!AUTHOR_PUBKEY) { - throw new Error("NEXT_PUBLIC_AUTHOR_PUBKEY is not defined in the environment variables"); -} - const relayUrls = [ "wss://nos.lol/", "wss://relay.damus.io/", diff --git a/src/hooks/nostrQueries/useZapsQuery.js b/src/hooks/nostrQueries/useCoursesZapsQuery.js similarity index 52% rename from src/hooks/nostrQueries/useZapsQuery.js rename to src/hooks/nostrQueries/useCoursesZapsQuery.js index 37d8c6d..978a2a7 100644 --- a/src/hooks/nostrQueries/useZapsQuery.js +++ b/src/hooks/nostrQueries/useCoursesZapsQuery.js @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'; import { useQuery } from '@tanstack/react-query'; import { useNDKContext } from '@/context/NDKContext'; -export function useZapsQuery({ events }) { +export function useCoursesZapsQuery({ event }) { const [isClient, setIsClient] = useState(false); const ndk = useNDKContext(); @@ -10,24 +10,28 @@ export function useZapsQuery({ events }) { setIsClient(true); }, []); - const fetchZapsFromNDK = async (events) => { + const fetchZapsFromNDK = async (event) => { + if (!ndk) { + console.error('NDK instance is null'); + return []; + } + + if (!event) { + console.error('No event provided'); + return []; + } + try { await ndk.connect(); - let zaps = []; - for (const event of events) { - const uniqueEvents = new Set(); - const filters = [{ kinds: [9735], "#e": [event.id] }, { kinds: [9735], "#a": [`${event.kind}:${event.id}:${event.d}`] }]; + const filters = [{ kinds: [9735], "#e": [event.id] }, { kinds: [9735], "#a": [`${event.kind}:${event.id}:${event.d}`] }]; - for (const filter of filters) { - const zapEvents = await ndk.fetchEvents(filter); - zapEvents.forEach(zap => uniqueEvents.add(zap)); - } - - zaps = [...zaps, ...Array.from(uniqueEvents)]; + for (const filter of filters) { + const zapEvents = await ndk.fetchEvents(filter); + zapEvents.forEach(zap => zaps.push(zap)); } - console.log('Zaps fetched:', zaps); + return zaps; } catch (error) { console.error('Error fetching zaps from NDK:', error); @@ -36,12 +40,12 @@ export function useZapsQuery({ events }) { }; const { data: zaps, isLoading: zapsLoading, error: zapsError, refetch: refetchZaps } = useQuery({ - queryKey: ['zaps', isClient], - queryFn: () => fetchZapsFromNDK(events), + queryKey: ['coursesZaps', isClient, event], + queryFn: () => fetchZapsFromNDK(event), staleTime: 1000 * 60 * 3, // 3 minutes cacheTime: 1000 * 60 * 60, // 1 hour enabled: isClient, - }) + }); return { zaps, zapsLoading, zapsError, refetchZaps } -} \ No newline at end of file +} diff --git a/src/hooks/nostrQueries/useResourceZapsQuery.js b/src/hooks/nostrQueries/useResourceZapsQuery.js new file mode 100644 index 0000000..0ca7883 --- /dev/null +++ b/src/hooks/nostrQueries/useResourceZapsQuery.js @@ -0,0 +1,51 @@ +import { useState, useEffect } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { useNDKContext } from '@/context/NDKContext'; + +export function useResourceZapsQuery({ event }) { + const [isClient, setIsClient] = useState(false); + const ndk = useNDKContext(); + + useEffect(() => { + setIsClient(true); + }, []); + + const fetchZapsFromNDK = async (event) => { + if (!ndk) { + console.error('NDK instance is null'); + return []; + } + + if (!event) { + console.error('No event provided'); + return []; + } + + try { + await ndk.connect(); + let zaps = []; + + const filters = [{ kinds: [9735], "#e": [event.id] }, { kinds: [9735], "#a": [`${event.kind}:${event.id}:${event.d}`] }]; + + for (const filter of filters) { + const zapEvents = await ndk.fetchEvents(filter); + zapEvents.forEach(zap => zaps.push(zap)); + } + + return zaps; + } catch (error) { + console.error('Error fetching zaps from NDK:', error); + return []; + } + }; + + const { data: zaps, isLoading: zapsLoading, error: zapsError, refetch: refetchZaps } = useQuery({ + queryKey: ['resourceZaps', isClient, event], + queryFn: () => fetchZapsFromNDK(event), + staleTime: 1000 * 60 * 3, // 3 minutes + cacheTime: 1000 * 60 * 60, // 1 hour + enabled: isClient, + }); + + return { zaps, zapsLoading, zapsError, refetchZaps } +} diff --git a/src/hooks/nostrQueries/useWorkshopsZapsQuery.js b/src/hooks/nostrQueries/useWorkshopsZapsQuery.js new file mode 100644 index 0000000..0519f75 --- /dev/null +++ b/src/hooks/nostrQueries/useWorkshopsZapsQuery.js @@ -0,0 +1,51 @@ +import { useState, useEffect } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { useNDKContext } from '@/context/NDKContext'; + +export function useWorkshopsZapsQuery({ event }) { + const [isClient, setIsClient] = useState(false); + const ndk = useNDKContext(); + + useEffect(() => { + setIsClient(true); + }, []); + + const fetchZapsFromNDK = async (event) => { + if (!ndk) { + console.error('NDK instance is null'); + return []; + } + + if (!event) { + console.error('No event provided'); + return []; + } + + try { + await ndk.connect(); + let zaps = []; + + const filters = [{ kinds: [9735], "#e": [event.id] }, { kinds: [9735], "#a": [`${event.kind}:${event.id}:${event.d}`] }]; + + for (const filter of filters) { + const zapEvents = await ndk.fetchEvents(filter); + zapEvents.forEach(zap => zaps.push(zap)); + } + + return zaps; + } catch (error) { + console.error('Error fetching zaps from NDK:', error); + return []; + } + }; + + const { data: zaps, isLoading: zapsLoading, error: zapsError, refetch: refetchZaps } = useQuery({ + queryKey: ['workshopsZaps', isClient, event], + queryFn: () => fetchZapsFromNDK(event), + staleTime: 1000 * 60 * 3, // 3 minutes + cacheTime: 1000 * 60 * 60, // 1 hour + enabled: isClient, + }); + + return { zaps, zapsLoading, zapsError, refetchZaps } +} diff --git a/src/pages/details/[slug].js b/src/pages/details/[slug].js index bc72b51..16be264 100644 --- a/src/pages/details/[slug].js +++ b/src/pages/details/[slug].js @@ -1,7 +1,5 @@ -"use client"; import React, { useEffect, useState } from 'react'; import { useRouter } from 'next/router'; -import { useNostr } from '@/hooks/useNostr'; import { parseEvent, findKind0Fields, hexToNpub } from '@/utils/nostr'; import { useImageProxy } from '@/hooks/useImageProxy'; import { getSatAmountFromInvoice } from '@/utils/lightning'; @@ -12,6 +10,7 @@ import { useLocalStorageWithEffect } from '@/hooks/useLocalStorage'; import Image from 'next/image'; import dynamic from 'next/dynamic'; import ZapThreadsWrapper from '@/components/ZapThreadsWrapper'; +import { useNDKContext } from '@/context/NDKContext'; import 'primeicons/primeicons.css'; const MDDisplay = dynamic( () => import("@uiw/react-markdown-preview"), @@ -35,21 +34,13 @@ export default function Details() { const [nAddress, setNAddress] = useState(null); const [zaps, setZaps] = useState([]); const [zapAmount, setZapAmount] = useState(0); + + const ndk = useNDKContext(); const [user] = useLocalStorageWithEffect('user', {}); - console.log('user:', user); const { returnImageProxy } = useImageProxy(); - const { fetchSingleEvent, fetchKind0, zapEvent, fetchZapsForEvent } = useNostr(); const router = useRouter(); - const handleZapEvent = async () => { - if (!event) return; - - const response = await zapEvent(event); - - console.log('zap response:', response); - } - useEffect(() => { if (typeof window === 'undefined') return; @@ -65,31 +56,51 @@ export default function Details() { const { slug } = router.query; const fetchEvent = async (slug) => { - console.log('slug:', slug); - const event = await fetchSingleEvent(slug); - console.log('event:', event); - if (event) { - setEvent(event); + try { + await ndk.connect(); + + const filter = { + ids: [slug] + } + + const event = await ndk.fetchEvent(filter); + + if (event) { + setEvent(event); + } + } catch (error) { + console.error('Error fetching event:', error); } }; - - fetchEvent(slug); + if (ndk) { + fetchEvent(slug); + } } - }, [router.isReady, router.query]); + }, [router.isReady, router.query, ndk]); useEffect(() => { const fetchAuthor = async (pubkey) => { - const author = await fetchKind0(pubkey); - const fields = await findKind0Fields(author); - console.log('fields:', fields); - if (fields) { - setAuthor(fields); + try { + await ndk.connect(); + + const filter = { + kinds: [0], + authors: [pubkey] + } + + const author = await ndk.fetchEvent(filter); + if (author) { + const fields = await findKind0Fields(JSON.parse(author.content)); + setAuthor(fields); + } + } catch (error) { + console.error('Error fetching author:', error); } } - if (event) { + if (event && ndk) { fetchAuthor(event.pubkey); } - }, [fetchKind0, event]); + }, [ndk, event]); useEffect(() => { if (event) { @@ -105,14 +116,13 @@ export default function Details() { kind: processedEvent.kind, identifier: processedEvent.d, }); - console.log('naddr:', naddr); setNAddress(naddr); } }, [processedEvent]); useEffect(() => { if (!zaps || zaps.length === 0) return; - + let total = 0; zaps.forEach((zap) => { const bolt11Tag = zap.tags.find(tag => tag[0] === "bolt11"); @@ -127,13 +137,31 @@ export default function Details() { useEffect(() => { const fetchZaps = async () => { - if (event) { - const zaps = await fetchZapsForEvent(event); + try { + const processed = parseEvent(event); + await ndk.connect(); + + const filters = [ + { + kinds: [9735], + "#e": [processed.id] + }, + { + kinds: [9734], + "#a": [`${processed.kind}:${processed.id}:${processed.d}`] + } + ] + + const zaps = await ndk.fetchEvents(filters); setZaps(zaps); + } catch (error) { + console.error('Error fetching zaps:', error); } } - fetchZaps(); - }, [fetchZapsForEvent, event]); + if (event && ndk) { + fetchZaps(); + } + }, [ndk, event]); return (
diff --git a/src/pages/index.js b/src/pages/index.js index eee49ca..e0fc9f7 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -18,8 +18,8 @@ export default function Home() {
- {/* - */} + +
);