diff --git a/src/components/CourseDetails.js b/src/components/CourseDetails.js
index 0173e79..c09a05b 100644
--- a/src/components/CourseDetails.js
+++ b/src/components/CourseDetails.js
@@ -2,7 +2,7 @@
import React, { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import { useNostr } from '@/hooks/useNostr';
-import { parseEvent, findKind0Fields, hexToNpub } from '@/utils/nostr';
+import { findKind0Fields } from '@/utils/nostr';
import { useImageProxy } from '@/hooks/useImageProxy';
import { Button } from 'primereact/button';
import { Tag } from 'primereact/tag';
@@ -31,9 +31,8 @@ export default function CourseDetails({processedEvent}) {
const [bitcoinConnect, setBitcoinConnect] = useState(false);
const [nAddress, setNAddress] = useState(null);
const [user] = useLocalStorageWithEffect('user', {});
- console.log('user:', user);
const { returnImageProxy } = useImageProxy();
- const { fetchSingleEvent, fetchKind0, zapEvent } = useNostr();
+ const { fetchKind0, zapEvent } = useNostr();
const router = useRouter();
@@ -76,7 +75,6 @@ export default function CourseDetails({processedEvent}) {
kind: processedEvent.kind,
identifier: processedEvent.d,
});
- console.log('naddr:', naddr);
setNAddress(naddr);
}
}, [processedEvent]);
diff --git a/src/components/content/carousels/CoursesCarousel.js b/src/components/content/carousels/CoursesCarousel.js
index b5fd68f..6ce793f 100644
--- a/src/components/content/carousels/CoursesCarousel.js
+++ b/src/components/content/carousels/CoursesCarousel.js
@@ -69,7 +69,7 @@ export default function CoursesCarousel() {
Courses
0 ? [{}, {}, {}] : [...processedCourses, ...processedCourses]}
+ value={!processedCourses.length > 0 ? [{}, {}, {}] : [...processedCourses]}
numVisible={2}
itemTemplate={!processedCourses.length > 0 ? TemplateSkeleton : CourseTemplate}
responsiveOptions={responsiveOptions} />
diff --git a/src/components/content/carousels/ResourcesCarousel.js b/src/components/content/carousels/ResourcesCarousel.js
index 8d7f379..cb91624 100644
--- a/src/components/content/carousels/ResourcesCarousel.js
+++ b/src/components/content/carousels/ResourcesCarousel.js
@@ -64,7 +64,7 @@ export default function ResourcesCarousel() {
return (
<>
Resources
- 0 ? [{}, {}, {}] : [...processedResources, ...processedResources]}
+ 0 ? [{}, {}, {}] : [...processedResources]}
numVisible={2}
itemTemplate={!processedResources.length > 0 ? TemplateSkeleton : ResourceTemplate}
responsiveOptions={responsiveOptions} />
diff --git a/src/components/content/carousels/WorkshopsCarousel.js b/src/components/content/carousels/WorkshopsCarousel.js
index 425bb40..651967f 100644
--- a/src/components/content/carousels/WorkshopsCarousel.js
+++ b/src/components/content/carousels/WorkshopsCarousel.js
@@ -66,7 +66,7 @@ export default function WorkshopsCarousel() {
return (
<>
Workshops
- 0 ? [{}, {}, {}] : [...processedWorkshops, ...processedWorkshops]}
+ 0 ? [{}, {}, {}] : [...processedWorkshops]}
numVisible={2}
itemTemplate={!processedWorkshops.length > 0 ? TemplateSkeleton : WorkshopTemplate}
responsiveOptions={responsiveOptions} />
diff --git a/src/components/zaps/ZapDisplay.js b/src/components/zaps/ZapDisplay.js
index d740e18..b1a6dc2 100644
--- a/src/components/zaps/ZapDisplay.js
+++ b/src/components/zaps/ZapDisplay.js
@@ -2,17 +2,19 @@ import React, { useRef } from 'react';
import { OverlayPanel } from 'primereact/overlaypanel';
import ZapForm from './ZapForm';
import { ProgressSpinner } from 'primereact/progressspinner';
-
const ZapDisplay = ({ zapAmount, event }) => {
const op = useRef(null);
return (
<>
- op.current.toggle(e)}>
-
-
- {zapAmount || zapAmount === 0 ? zapAmount : }
-
+ op.current.toggle(e)}>
+
+ {zapAmount || zapAmount === 0 ? (
+ zapAmount
+ ) : (
+
+ )}
+
@@ -20,4 +22,4 @@ const ZapDisplay = ({ zapAmount, event }) => {
)
}
-export default ZapDisplay;
\ No newline at end of file
+export default ZapDisplay;
diff --git a/src/hooks/useNostr.js b/src/hooks/useNostr.js
index ed9a336..bcc4b7a 100644
--- a/src/hooks/useNostr.js
+++ b/src/hooks/useNostr.js
@@ -24,6 +24,9 @@ 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;
@@ -45,7 +48,23 @@ export function useNostr() {
if (!pool) return;
const subscriptionFn = () => {
- return pool.subscribeMany(defaultRelays, filters, opts);
+ // 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;
};
subscriptionQueue.current.push(subscriptionFn);
@@ -54,6 +73,19 @@ 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;
diff --git a/src/hooks/useNostrOld.js b/src/hooks/useNostrOld.js
deleted file mode 100644
index 77db210..0000000
--- a/src/hooks/useNostrOld.js
+++ /dev/null
@@ -1,386 +0,0 @@
-import { useState, useEffect, useRef } from "react";
-import { SimplePool, nip19, verifyEvent, nip57 } from "nostr-tools";
-import axios from "axios";
-import { useToast } from "./useToast";
-
-const initialRelays = [
- "wss://nos.lol/",
- "wss://relay.damus.io/",
- "wss://relay.snort.social/",
- "wss://relay.nostr.band/",
- "wss://nostr.mutinywallet.com/",
- "wss://relay.mutinywallet.com/",
- "wss://relay.primal.net/"
-];
-
-const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY;
-
-export const useNostr = () => {
- const [relays, setRelays] = useState(initialRelays);
- const [relayStatuses, setRelayStatuses] = useState({});
- const [events, setEvents] = useState({
- resources: [],
- workshops: [],
- courses: [],
- streams: [],
- zaps: []
- });
-
- const { showToast } = useToast();
-
- const pool = useRef(new SimplePool({ seenOnEnabled: true }));
- const subscriptions = useRef([]);
-
- const getRelayStatuses = () => {
- if (pool.current && pool.current._conn) {
- const statuses = {};
-
- for (const url in pool.current._conn) {
- const relay = pool.current._conn[url];
- statuses[url] = relay.status; // Assuming 'status' is an accessible field in Relay object
- }
-
- setRelayStatuses(statuses);
- }
- };
-
- const updateRelays = async (newRelays) => {
- // Set new relays
- setRelays(newRelays);
-
- // Ensure the relays are connected before using them
- await Promise.all(newRelays.map(relay => pool.current.ensureRelay(relay)));
- };
-
- const fetchEvents = async (filter, updateDataField, hasRequiredTags) => {
- try {
- const sub = pool.current.subscribeMany(relays, filter, {
- onevent: async (event) => {
- const shouldInclude = await hasRequiredTags(event.tags);
- if (shouldInclude) {
- setEvents(prevData => ({
- ...prevData,
- [updateDataField]: [...prevData[updateDataField], event]
- }));
- }
- },
- onerror: (error) => {
- console.error(`Error fetching ${updateDataField}:`, error);
- },
- onclose: () => {
- // Handle connection closure and retry if needed
- console.log("Connection closed");
- // Implement retry logic here
- },
- oneose: () => {
- console.log("Subscription closed");
- sub.close();
- }
- });
-
- // Store the subscription in the ref for cleanup
- subscriptions.current.push(sub);
- } catch (error) {
- console.error(`Error in fetchEvents for ${updateDataField}:`, error);
- }
- };
-
- // zaps
- // 1. get the author from the content
- // 2. get the author's kind0
- // 3. get the author's lud16 if available
- // 4. Make a get request to the lud16 endpoint and ensure that allowNostr is true
- // 5. Create zap request event and sign it
- // 6. Send to the callback url as a get req with the nostr event as a query param
- // 7. get the invoice back and pay it with webln
- // 8. listen for the zap receipt event and update the UI
-
- const zapEvent = async (event) => {
- const kind0 = await fetchKind0([{ authors: [event.pubkey], kinds: [0] }], {});
-
- if (Object.keys(kind0).length === 0) {
- console.error('Error fetching kind0');
- return;
- }
-
- if (kind0?.lud16) {
- const lud16Username = kind0.lud16.split('@')[0];
- const lud16Domain = kind0.lud16.split('@')[1];
-
- const lud16Url = `https://${lud16Domain}/.well-known/lnurlp/${lud16Username}`;
-
- const response = await axios.get(lud16Url);
-
- if (response.data.allowsNostr) {
- const zapReq = nip57.makeZapRequest({
- profile: event.pubkey,
- event: event.id,
- amount: 1000,
- relays: relays,
- comment: 'Plebdevs Zap'
- });
-
- console.log('zapReq:', zapReq);
-
- const signedEvent = await window?.nostr.signEvent(zapReq);
-
- const callbackUrl = response.data.callback;
-
- const zapRequestAPICall = `${callbackUrl}?amount=${1000}&nostr=${encodeURI(JSON.stringify(signedEvent))}`;
-
- const invoiceResponse = await axios.get(zapRequestAPICall);
-
- if (invoiceResponse?.data?.pr) {
- const invoice = invoiceResponse.data.pr;
-
- const enabled = await window?.webln?.enable();
-
- console.log('webln enabled:', enabled);
-
- const payInvoiceResponse = await window?.webln?.sendPayment(invoice);
-
- console.log('payInvoiceResponse:', payInvoiceResponse);
- } else {
- console.error('Error fetching invoice');
- showToast('error', 'Error', 'Error fetching invoice');
- }
- }
- } else if (kind0?.lud06) {
- // handle lnurlpay
- } else {
- showToast('error', 'Error', 'User has no Lightning Address or LNURL');
- return;
- }
-
- }
-
- const fetchZapsForEvent = async (eventId) => {
- const filter = [{ kinds: [9735], "#e": [eventId] }];
- fetchEvents(filter, 'zaps', () => true);
- }
-
- // Fetch resources, workshops, courses, and streams with appropriate filters and update functions
- const fetchResources = async () => {
- const filter = [{ kinds: [30023], authors: [AUTHOR_PUBKEY] }];
- const hasRequiredTags = async (eventData) => {
- const hasPlebDevs = eventData.some(([tag, value]) => tag === "t" && value === "plebdevs");
- const hasResource = eventData.some(([tag, value]) => tag === "t" && value === "resource");
- if (hasPlebDevs && hasResource) {
- const resourceId = eventData.find(([tag]) => tag === "d")?.[1];
- if (resourceId) {
- try {
- const response = await axios.get(`/api/resources/${resourceId}`);
- return response.status === 200;
- } catch (error) {
- // Handle 404 or other errors gracefully
- return false;
- }
- }
- }
- return false;
- };
- fetchEvents(filter, 'resources', hasRequiredTags);
- };
-
- const fetchWorkshops = async () => {
- const filter = [{ kinds: [30023], authors: [AUTHOR_PUBKEY] }];
- const hasRequiredTags = async (eventData) => {
- const hasPlebDevs = eventData.some(([tag, value]) => tag === "t" && value === "plebdevs");
- const hasWorkshop = eventData.some(([tag, value]) => tag === "t" && value === "workshop");
- if (hasPlebDevs && hasWorkshop) {
- const workshopId = eventData.find(([tag]) => tag === "d")?.[1];
- if (workshopId) {
- try {
- const response = await axios.get(`/api/resources/${workshopId}`);
- return response.status === 200;
- } catch (error) {
- // Handle 404 or other errors gracefully
- return false;
- }
- }
- }
- return false;
- };
- fetchEvents(filter, 'workshops', hasRequiredTags);
- };
-
- const fetchCourses = async () => {
- const filter = [{ kinds: [30023], authors: [AUTHOR_PUBKEY] }];
- const hasRequiredTags = async (eventData) => {
- const hasPlebDevs = eventData.some(([tag, value]) => tag === "t" && value === "plebdevs");
- const hasCourse = eventData.some(([tag, value]) => tag === "t" && value === "course");
- if (hasPlebDevs && hasCourse) {
- const courseId = eventData.find(([tag]) => tag === "d")?.[1];
- if (courseId) {
- // try {
- // const response = await axios.get(`/api/resources/${courseId}`);
- // return response.status === 200;
- // } catch (error) {
- // // Handle 404 or other errors gracefully
- // return false;
- // }
- return true;
- }
- }
- return false;
- };
- fetchEvents(filter, 'courses', hasRequiredTags);
- };
-
- // const fetchStreams = () => {
- // const filter = [{kinds: [30311], authors: [AUTHOR_PUBKEY]}];
- // const hasRequiredTags = (eventData) => eventData.some(([tag, value]) => tag === "t" && value === "plebdevs");
- // fetchEvents(filter, 'streams', hasRequiredTags);
- // }
-
- const fetchKind0 = async (criteria, params) => {
- return new Promise((resolve, reject) => {
- const events = [];
- const timeoutDuration = 1000;
-
- const sub = pool.current.subscribeMany(relays, criteria, {
- ...params,
- onevent: (event) => {
- events.push(event);
- },
- onerror: (error) => {
- reject(error);
- }
- });
-
- // Set a timeout to sort and resolve with the most recent event
- setTimeout(() => {
- if (events.length === 0) {
- resolve(null); // or reject based on your needs
- } else {
- events.sort((a, b) => b.created_at - a.created_at); // Sort in descending order
- const mostRecentEventContent = JSON.parse(events[0].content);
- resolve(mostRecentEventContent);
- }
- }, timeoutDuration);
- });
- };
-
- const fetchSingleEvent = async (id) => {
- return new Promise((resolve, reject) => {
- const sub = pool.current.subscribeMany(relays, [{ ids: [id] }], {
- onevent: (event) => {
- resolve(event);
- },
- onerror: (error) => {
- reject(error);
- },
- onclose: () => {
- // Handle connection closure and retry if needed
- console.log("Connection closed");
- // Implement retry logic here
- },
- oneose: () => {
- console.log("Subscription closed");
- sub.close();
- }
- });
- });
- }
-
- const publishEvent = async (relay, signedEvent) => {
- console.log('publishing event to', relay);
- return new Promise((resolve, reject) => {
- const timeout = 3000
- const wsRelay = new window.WebSocket(relay)
- let timer
- let isMessageSentSuccessfully = false
-
- function timedout() {
- clearTimeout(timer)
- wsRelay.close()
- reject(new Error(`relay timeout for ${relay}`))
- }
-
- timer = setTimeout(timedout, timeout)
-
- wsRelay.onopen = function () {
- clearTimeout(timer)
- timer = setTimeout(timedout, timeout)
- wsRelay.send(JSON.stringify(['EVENT', signedEvent]))
- }
-
- wsRelay.onmessage = function (msg) {
- const m = JSON.parse(msg.data)
- if (m[0] === 'OK') {
- isMessageSentSuccessfully = true
- clearTimeout(timer)
- wsRelay.close()
- console.log('Successfully sent event to', relay)
- resolve()
- }
- }
-
- wsRelay.onerror = function (error) {
- clearTimeout(timer)
- console.log(error)
- reject(new Error(`relay error: Failed to send to ${relay}`))
- }
-
- wsRelay.onclose = function () {
- clearTimeout(timer)
- if (!isMessageSentSuccessfully) {
- reject(new Error(`relay error: Failed to send to ${relay}`))
- }
- }
- })
- };
-
-
- const publishAll = async (signedEvent) => {
- try {
- const promises = relays.map(relay => publishEvent(relay, signedEvent));
- const results = await Promise.allSettled(promises)
- const successfulRelays = []
- const failedRelays = []
-
- results.forEach((result, i) => {
- if (result.status === 'fulfilled') {
- successfulRelays.push(relays[i])
- showToast('success', `published to ${relays[i]}`)
- } else {
- failedRelays.push(relays[i])
- showToast('error', `failed to publish to ${relays[i]}`)
- }
- })
-
- return { successfulRelays, failedRelays }
- } catch (error) {
- console.error('Error publishing event:', error);
- }
- };
-
-
- useEffect(() => {
- getRelayStatuses();
-
- const currentSubscriptions = subscriptions.current;
-
- return () => {
- // Close all active subscriptions on cleanup
- currentSubscriptions.forEach((sub) => {
- sub.close();
- });
- };
- }, []);
-
- return {
- updateRelays,
- fetchSingleEvent,
- publishAll,
- fetchKind0,
- fetchResources,
- fetchCourses,
- fetchWorkshops,
- // fetchStreams,
- zapEvent,
- fetchZapsForEvent,
- getRelayStatuses,
- events
- };
-};
\ No newline at end of file
diff --git a/src/pages/draft/[slug]/index.js b/src/pages/draft/[slug]/index.js
index d63643f..82fce22 100644
--- a/src/pages/draft/[slug]/index.js
+++ b/src/pages/draft/[slug]/index.js
@@ -196,7 +196,7 @@ export default function Details() {
...draft.topics.map(topic => ['t', topic]),
['published_at', Math.floor(Date.now() / 1000).toString()],
// Include price and location tags only if price is present
- ...(draft?.price ? [['price', draft.price], ['location', `https://plebdevs.com/resource/${draft.id}`]] : []),
+ ...(draft?.price ? [['price', draft.price], ['location', `https://plebdevs.com/details/${draft.id}`]] : []),
]
};
type = 'resource';
diff --git a/src/pages/login.js b/src/pages/login.js
index 970d4bb..096d8a5 100644
--- a/src/pages/login.js
+++ b/src/pages/login.js
@@ -2,7 +2,6 @@ import React from "react";
import { Button } from 'primereact/button';
import { useLogin } from "@/hooks/useLogin";
-
const Login = () => {
const { nostrLogin, anonymousLogin } = useLogin();
return (
diff --git a/src/pages/resource/[slug].js b/src/pages/resource/[slug].js
deleted file mode 100644
index b434a39..0000000
--- a/src/pages/resource/[slug].js
+++ /dev/null
@@ -1,46 +0,0 @@
-import React, { useEffect, useState } from "react";
-import { useRouter } from "next/router";
-import { useNostr } from "@/hooks/useNostr";
-import { parseEvent } from "@/utils/nostr";
-
-
-const Resource = () => {
- const [resource, setResource] = useState(null);
-
- const router = useRouter();
- const { fetchSingleEvent } = useNostr();
-
- const { slug } = router.query;
-
- console.log('slug:', slug);
-
- useEffect(() => {
- const getResource = async () => {
- if (slug) {
- const fetchedResource = await fetchSingleEvent(slug);
- console.log('fetchedResource:', fetchedResource);
- const formattedResource = parseEvent(fetchedResource);
- console.log('formattedResource:', formattedResource.summary);
- setResource(formattedResource);
- }
- };
-
- if (slug && !resource) {
- getResource();
- }
- }, [slug]);
-
- return (
-
-
{resource?.title}
-
{resource?.summary}
-
- {
- resource?.content &&
- }
-
-
- );
-}
-
-export default Resource;
\ No newline at end of file