diff --git a/src/pages/details/[slug]/index.js b/src/pages/details/[slug]/index.js index 19402b0..540617a 100644 --- a/src/pages/details/[slug]/index.js +++ b/src/pages/details/[slug]/index.js @@ -1,189 +1,107 @@ -import React, { useEffect, useState } from 'react'; -import { useRouter } from 'next/router'; +import React, { useState, useCallback, useEffect } from "react"; +import DocumentDetails from "@/components/content/documents/DocumentDetails"; +import VideoDetails from "@/components/content/videos/VideoDetails"; import { parseEvent, findKind0Fields } from '@/utils/nostr'; -import { nip19, nip04 } from 'nostr-tools'; +import { nip19 } from 'nostr-tools'; import { useSession } from 'next-auth/react'; -import ZapThreadsWrapper from '@/components/ZapThreadsWrapper'; -import { useToast } from '@/hooks/useToast'; -import { useNDKContext } from '@/context/NDKContext'; -import VideoDetails from '@/components/content/videos/VideoDetails'; -import DocumentDetails from '@/components/content/documents/DocumentDetails'; +import { useNDKContext } from "@/context/NDKContext"; +import { useDecryptContent } from "@/hooks/encryption/useDecryptContent"; +import { useToast } from "@/hooks/useToast"; +import { useRouter } from "next/router"; import { ProgressSpinner } from 'primereact/progressspinner'; -import appConfig from "@/config/appConfig"; -import { useDecryptContent } from '@/hooks/encryption/useDecryptContent'; -import { useEncryptContent } from '@/hooks/encryption/useEncryptContent'; -import 'primeicons/primeicons.css'; +import ZapThreadsWrapper from '@/components/ZapThreadsWrapper'; +import { appConfig } from "@/config/appConfig"; -export default function Details() { +const Details = () => { const [event, setEvent] = useState(null); - const [processedEvent, setProcessedEvent] = useState({}); const [author, setAuthor] = useState(null); const [nAddress, setNAddress] = useState(null); - const [paidResource, setPaidResource] = useState(false); const [decryptedContent, setDecryptedContent] = useState(null); const [authorView, setAuthorView] = useState(false); const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - const { ndk, addSigner } = useNDKContext(); - const { data: session, update } = useSession(); - const [user, setUser] = useState(null); + const { data: session } = useSession(); + const { ndk } = useNDKContext(); const { decryptContent } = useDecryptContent(); - const { encryptContent } = useEncryptContent(); + const router = useRouter(); const { showToast } = useToast(); - const router = useRouter(); - - useEffect(() => { - if (session) { - setUser(session.user); + const fetchAuthor = useCallback(async (pubkey) => { + if (!pubkey) return; + const author = await ndk.getUser({ pubkey }); + const profile = await author.fetchProfile(); + const fields = await findKind0Fields(profile); + if (fields) { + setAuthor(fields); } - }, [session]); + }, [ndk]); useEffect(() => { - if (processedEvent.price) { - setPaidResource(true); - } - }, [processedEvent]); - - useEffect(() => { - const decrypt = async () => { - if (paidResource && processedEvent.content) { - // Check if user is subscribed first - if (user?.role?.subscribed) { - const decryptedContent = await decryptContent(processedEvent.content); - setDecryptedContent(decryptedContent); - } - // If not subscribed, check if they have purchased - else if (user?.purchased?.some(purchase => purchase.resourceId === processedEvent.d)) { - const decryptedContent = await decryptContent(processedEvent.content); - setDecryptedContent(decryptedContent); - } - // If neither subscribed nor purchased, decryptedContent remains null - } - }; - - decrypt(); - }, [user, paidResource, processedEvent]); - - useEffect(() => { - if (router.isReady) { - const { slug } = router.query; - - - if (!slug) { - return; - } - - let id; - - if (slug.includes("naddr")) { - const { data } = nip19.decode(slug) - - if (!data) { - showToast('error', 'Error', 'Resource not found'); - return; - } - - id = data?.identifier; - } else { - id = slug; - } - - const fetchEvent = async (id, retryCount = 0) => { - setLoading(true); - setError(null); - try { - await ndk.connect(); - - const filter = { - ids: [id] - } - - const event = await ndk.fetchEvent(filter); - - if (event) { - setEvent(event); - if (user && user.pubkey === event.pubkey) { - setAuthorView(true); - if (event.kind === 30402) { - const decryptedContent = await decryptContent(event.content); - setDecryptedContent(decryptedContent); - } - } - } else { - if (retryCount < 1) { - // Wait for 2 seconds before retrying - await new Promise(resolve => setTimeout(resolve, 3000)); - return fetchEvent(id, retryCount + 1); - } else { - setError("Event not found"); - } - } - } catch (error) { - console.error('Error fetching event:', error); - if (retryCount < 1) { - // Wait for 2 seconds before retrying - await new Promise(resolve => setTimeout(resolve, 3000)); - return fetchEvent(id, retryCount + 1); - } else { - setError("Failed to fetch event. Please try again."); - } - } finally { - setLoading(false); - } - }; - - if (ndk && id) { - fetchEvent(id); - } - } - }, [router.isReady, router.query, ndk, user]); - - useEffect(() => { - const fetchAuthor = async (pubkey) => { - 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)); - console.log("fields", fields); - setAuthor(fields); - } - } catch (error) { - console.error('Error fetching author:', error); - } - } - if (event && ndk) { - fetchAuthor(event.pubkey); - } - }, [ndk, event]); - - useEffect(() => { - if (event) { - const parsedEvent = parseEvent(event); - console.log("parsedEvent", parsedEvent); - setProcessedEvent(parsedEvent); - } - }, [event]); - - useEffect(() => { - if (processedEvent?.d) { + if (event?.d && !nAddress) { const naddr = nip19.naddrEncode({ - pubkey: processedEvent.pubkey, - kind: processedEvent.kind, - identifier: processedEvent.d, + pubkey: event.pubkey, + kind: event.kind, + identifier: event.d, relayUrls: appConfig.defaultRelayUrls }); setNAddress(naddr); } - }, [processedEvent]); + }, [event, nAddress]); + + useEffect(() => { + const fetchAndProcessEvent = async () => { + if (!router.isReady || !router.query.slug) return; + + const { slug } = router.query; + let id; + + if (slug.includes("naddr")) { + const { data } = nip19.decode(slug); + if (!data) { + showToast('error', 'Error', 'Resource not found'); + setLoading(false); + return; + } + id = data?.identifier; + setNAddress(slug); + } else { + id = slug; + } + + try { + await ndk.connect(); + const event = await ndk.fetchEvent({ ids: [id] }); + + if (event) { + const parsedEvent = parseEvent(event); + setEvent(parsedEvent); + await fetchAuthor(event.pubkey); + + const isAuthor = session?.user?.pubkey === event.pubkey; + setAuthorView(isAuthor); + + if (parsedEvent.price || (isAuthor && event.kind === 30402)) { + const shouldDecrypt = isAuthor || + session?.user?.role?.subscribed || + session?.user?.purchased?.some(purchase => purchase.resourceId === event.d); + + if (shouldDecrypt) { + const decrypted = await decryptContent(event.content); + setDecryptedContent(decrypted); + } + } + } else { + showToast('error', 'Error', 'Event not found'); + } + } catch (error) { + console.error('Error fetching event:', error); + showToast('error', 'Error', 'Failed to fetch event. Please try again.'); + } finally { + setLoading(false); + } + }; + + fetchAndProcessEvent(); + }, [router.isReady, router.query, ndk, session, decryptContent, fetchAuthor, showToast]); const handlePaymentSuccess = async (response, newResource) => { if (response && response?.preimage) { @@ -200,60 +118,42 @@ export default function Details() { } if (loading) { - return