diff --git a/package-lock.json b/package-lock.json
index a174823..7749092 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,9 +7,11 @@
"": {
"name": "plebdevs",
"version": "0.1.0",
+ "hasInstallScript": true,
"dependencies": {
"@getalby/bitcoin-connect-react": "^3.5.3",
"@prisma/client": "^5.17.0",
+ "@tanstack/react-query": "^5.51.21",
"@uiw/react-markdown-preview": "^5.1.2",
"@uiw/react-md-editor": "^3.11.0",
"axios": "^1.7.2",
@@ -1216,6 +1218,32 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@tanstack/query-core": {
+ "version": "5.51.21",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.51.21.tgz",
+ "integrity": "sha512-POQxm42IUp6n89kKWF4IZi18v3fxQWFRolvBA6phNVmA8psdfB1MvDnGacCJdS+EOX12w/CyHM62z//rHmYmvw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/react-query": {
+ "version": "5.51.21",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.51.21.tgz",
+ "integrity": "sha512-Q/V81x3sAYgCsxjwOkfLXfrmoG+FmDhLeHH5okC/Bp8Aaw2c33lbEo/mMcMnkxUPVtB2FLpzHT0tq3c+OlZEbw==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/query-core": "5.51.21"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0"
+ }
+ },
"node_modules/@types/debug": {
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
diff --git a/package.json b/package.json
index 21fc4c0..f5b200f 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,7 @@
"dependencies": {
"@getalby/bitcoin-connect-react": "^3.5.3",
"@prisma/client": "^5.17.0",
- "@tanstack/react-query": "^5.0.0",
+ "@tanstack/react-query": "^5.51.21",
"@uiw/react-markdown-preview": "^5.1.2",
"@uiw/react-md-editor": "^3.11.0",
"axios": "^1.7.2",
@@ -35,4 +35,4 @@
"postcss": "^8",
"tailwindcss": "^3.4.1"
}
-}
\ No newline at end of file
+}
diff --git a/src/components/content/carousels/CoursesCarousel.js b/src/components/content/carousels/CoursesCarousel.js
index 4e29689..971e6cf 100644
--- a/src/components/content/carousels/CoursesCarousel.js
+++ b/src/components/content/carousels/CoursesCarousel.js
@@ -1,9 +1,10 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, use } from 'react';
import { Carousel } from 'primereact/carousel';
import { parseCourseEvent } from '@/utils/nostr';
import { useNostr } from '@/hooks/useNostr';
import CourseTemplate from '@/components/content/carousels/templates/CourseTemplate';
import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton';
+import { useNostrQueries } from '@/hooks/useNostrQueries';
const responsiveOptions = [
{
@@ -25,15 +26,26 @@ const responsiveOptions = [
export default function CoursesCarousel() {
const [processedCourses, setProcessedCourses] = useState([]);
- const { fetchCourses, fetchZapsForEvents } = useNostr();
+ const { fetchZapsForEvents } = useNostr();
+ const { courses, coursesError, zapsForEvents, refetchZapsForEvents } = useNostrQueries()
+
+ useEffect(() => {
+ if (courses && courses.length > 0) {
+ refetchZapsForEvents(courses);
+ }
+ }, [courses]);
+
+ useEffect(() => {
+ console.log('zapsForEvents:', zapsForEvents);
+ }, [zapsForEvents]);
useEffect(() => {
const fetch = async () => {
try {
- const fetchedCourses = await fetchCourses();
- if (fetchedCourses && fetchedCourses.length > 0) {
+ if ( courses && courses.length > 0) {
+ console.log('courses:', courses);
// First process the courses to be ready for display
- const processedCourses = fetchedCourses.map(course => parseCourseEvent(course));
+ const processedCourses = courses.map(course => parseCourseEvent(course));
// Fetch zaps for all processed courses at once
const allZaps = await fetchZapsForEvents(processedCourses);
@@ -62,7 +74,11 @@ export default function CoursesCarousel() {
}
};
fetch();
- }, [fetchCourses, fetchZapsForEvents]);
+ }, [courses]);
+
+ if (coursesError) {
+ return
Error: {coursesError.message}
+ }
return (
<>
diff --git a/src/components/content/carousels/ResourcesCarousel.js b/src/components/content/carousels/ResourcesCarousel.js
index 0d0d0e2..60e124b 100644
--- a/src/components/content/carousels/ResourcesCarousel.js
+++ b/src/components/content/carousels/ResourcesCarousel.js
@@ -4,6 +4,7 @@ import { useNostr } from '@/hooks/useNostr';
import { parseEvent } from '@/utils/nostr';
import ResourceTemplate from '@/components/content/carousels/templates/ResourceTemplate';
import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton';
+import { useNostrQueries } from '@/hooks/useNostrQueries';
const responsiveOptions = [
{
@@ -25,14 +26,16 @@ const responsiveOptions = [
export default function ResourcesCarousel() {
const [processedResources, setProcessedResources] = useState([]);
- const { fetchResources, fetchZapsForEvents } = useNostr();
+ const { fetchZapsForEvents } = useNostr();
+ const { resources, resourcesError, refetchResources } = useNostrQueries()
useEffect(() => {
const fetch = async () => {
try {
- const fetchedResources = await fetchResources();
- if (fetchedResources && fetchedResources.length > 0) {
- const processedResources = fetchedResources.map(resource => parseEvent(resource));
+ if (resources && resources.length > 0) {
+ const processedResources = resources.map(resource => parseEvent(resource));
+
+ console.log('processedResources:', processedResources);
const allZaps = await fetchZapsForEvents(processedResources);
@@ -51,14 +54,18 @@ export default function ResourcesCarousel() {
setProcessedResources(resourcesWithZaps);
} else {
- console.log('No resources fetched or empty array returned');
+ refetchResources();
}
} catch (error) {
console.error('Error fetching resources:', error);
}
};
fetch();
- }, [fetchResources, fetchZapsForEvents]); // Assuming fetchZapsForEvents is adjusted to handle resources
+ }, [resources]);
+
+ if (resourcesError) {
+ return Error: {resourcesError.message}
+ }
return (
diff --git a/src/components/content/carousels/WorkshopsCarousel.js b/src/components/content/carousels/WorkshopsCarousel.js
index efa0007..453bee9 100644
--- a/src/components/content/carousels/WorkshopsCarousel.js
+++ b/src/components/content/carousels/WorkshopsCarousel.js
@@ -1,11 +1,12 @@
import React, { useState, useEffect } from 'react';
import { Carousel } from 'primereact/carousel';
import { useRouter } from 'next/router';
-import { useNostr } from '@/hooks/useNostr';
import { useImageProxy } from '@/hooks/useImageProxy';
+import { useNostr } from '@/hooks/useNostr';
import { parseEvent } from '@/utils/nostr';
import WorkshopTemplate from '@/components/content/carousels/templates/WorkshopTemplate';
import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton';
+import { useNostrQueries } from '@/hooks/useNostrQueries';
const responsiveOptions = [
{
@@ -26,15 +27,17 @@ const responsiveOptions = [
];
export default function WorkshopsCarousel() {
- const [processedWorkshops, setProcessedWorkshops] = useState([]);
- const { fetchWorkshops, fetchZapsForEvents } = useNostr();
+ const [processedWorkshops, setProcessedWorkshops] = useState([])
+
+ const { workshops, workshopsError } = useNostrQueries()
+ const { fetchZapsForEvents } = useNostr()
useEffect(() => {
const fetch = async () => {
try {
- const fetchedWorkshops = await fetchWorkshops();
- if (fetchedWorkshops && fetchedWorkshops.length > 0) {
- const processedWorkshops = fetchedWorkshops.map(workshop => parseEvent(workshop));
+ console.debug('workshops', workshops);
+ if (workshops && workshops.length > 0) {
+ const processedWorkshops = workshops.map(workshop => parseEvent(workshop));
const allZaps = await fetchZapsForEvents(processedWorkshops);
@@ -60,8 +63,11 @@ export default function WorkshopsCarousel() {
}
};
fetch();
- }, [fetchWorkshops, fetchZapsForEvents]); // Assuming fetchZapsForEvents is adjusted to handle workshops
-
+ }, [workshops]);
+
+ if (workshopsError) {
+ return Error: {workshopsError.message}
+ }
return (
<>
diff --git a/src/hooks/useLocalStorage.js b/src/hooks/useLocalStorage.js
index 30b9569..b0c4277 100644
--- a/src/hooks/useLocalStorage.js
+++ b/src/hooks/useLocalStorage.js
@@ -1,3 +1,4 @@
+"use client";
import { useState, useEffect } from 'react';
// This version of the hook initializes state without immediately attempting to read from localStorage
diff --git a/src/hooks/useNostr.js b/src/hooks/useNostr.js
index ed9a336..cfa52c6 100644
--- a/src/hooks/useNostr.js
+++ b/src/hooks/useNostr.js
@@ -1,6 +1,5 @@
-import { useState, useEffect, useCallback, useContext, useRef } from 'react';
+import { useCallback, useContext, useRef } from 'react';
import axios from 'axios';
-import { nip57, nip19 } from 'nostr-tools';
import { NostrContext } from '@/context/NostrContext';
import { lnurlEncode } from '@/utils/lnurl';
import { parseEvent } from '@/utils/nostr';
@@ -16,8 +15,6 @@ const defaultRelays = [
"wss://relay.primal.net/"
];
-const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY;
-
export function useNostr() {
const pool = useContext(NostrContext);
const subscriptionQueue = useRef([]);
@@ -366,117 +363,6 @@ export function useNostr() {
[fetchKind0]
);
- const fetchResources = useCallback(async () => {
- const filter = [{ kinds: [30023, 30402], authors: [AUTHOR_PUBKEY] }];
- const hasRequiredTags = (tags) => {
- const hasPlebDevs = tags.some(([tag, value]) => tag === "t" && value === "plebdevs");
- const hasResource = tags.some(([tag, value]) => tag === "t" && value === "resource");
- return hasPlebDevs && hasResource;
- };
-
- return new Promise((resolve, reject) => {
- let resources = [];
- const subscription = subscribe(
- filter,
- {
- onevent: (event) => {
- if (hasRequiredTags(event.tags)) {
- resources.push(event);
- }
- },
- onerror: (error) => {
- console.error('Error fetching resources:', error);
- // Don't resolve here, just log the error
- },
- onclose: () => {
- // Don't resolve here either
- },
- },
- 2000 // Adjust the timeout value as needed
- );
-
- // Set a timeout to resolve the promise after collecting events
- setTimeout(() => {
- subscription?.close();
- resolve(resources);
- }, 2000); // Adjust the timeout value as needed
- });
- }, [subscribe]);
-
- const fetchWorkshops = useCallback(async () => {
- const filter = [{ kinds: [30023, 30402], authors: [AUTHOR_PUBKEY] }];
- const hasRequiredTags = (tags) => {
- const hasPlebDevs = tags.some(([tag, value]) => tag === "t" && value === "plebdevs");
- const hasWorkshop = tags.some(([tag, value]) => tag === "t" && value === "workshop");
- return hasPlebDevs && hasWorkshop;
- };
-
- return new Promise((resolve, reject) => {
- let workshops = [];
- const subscription = subscribe(
- filter,
- {
- onevent: (event) => {
- if (hasRequiredTags(event.tags)) {
- workshops.push(event);
- }
- },
- onerror: (error) => {
- console.error('Error fetching workshops:', error);
- // Don't resolve here, just log the error
- },
- onclose: () => {
- // Don't resolve here either
- },
- },
- 2000 // Adjust the timeout value as needed
- );
-
- setTimeout(() => {
- subscription?.close();
- resolve(workshops);
- }, 2000); // Adjust the timeout value as needed
- });
- }, [subscribe]);
-
- const fetchCourses = useCallback(async () => {
- const filter = [{ kinds: [30004], authors: [AUTHOR_PUBKEY] }];
- // Do we need required tags for courses? community instead?
- // const hasRequiredTags = (tags) => {
- // const hasPlebDevs = tags.some(([tag, value]) => tag === "t" && value === "plebdevs");
- // const hasCourse = tags.some(([tag, value]) => tag === "t" && value === "course");
- // return hasPlebDevs && hasCourse;
- // };
-
- return new Promise((resolve, reject) => {
- let courses = [];
- const subscription = subscribe(
- filter,
- {
- onevent: (event) => {
- // if (hasRequiredTags(event.tags)) {
- // courses.push(event);
- // }
- courses.push(event);
- },
- onerror: (error) => {
- console.error('Error fetching courses:', error);
- // Don't resolve here, just log the error
- },
- onclose: () => {
- // Don't resolve here either
- },
- },
- 2000 // Adjust the timeout value as needed
- );
-
- setTimeout(() => {
- subscription?.close();
- resolve(courses);
- }, 2000); // Adjust the timeout value as needed
- });
- }, [subscribe]);
-
const publishResource = useCallback(
async (resourceEvent) => {
const published = await publish(resourceEvent);
@@ -561,5 +447,5 @@ export function useNostr() {
[publish]
);
- return { subscribe, publish, fetchSingleEvent, fetchSingleNaddrEvent, fetchZapsForEvent, fetchKind0, fetchResources, fetchWorkshops, fetchCourses, zapEvent, fetchZapsForEvents, publishResource, publishCourse };
+ return { subscribe, publish, fetchSingleEvent, fetchSingleNaddrEvent, fetchZapsForEvent, fetchKind0, zapEvent, fetchZapsForEvents, publishResource, publishCourse };
}
\ No newline at end of file
diff --git a/src/hooks/useNostrQueries.js b/src/hooks/useNostrQueries.js
new file mode 100644
index 0000000..9253f85
--- /dev/null
+++ b/src/hooks/useNostrQueries.js
@@ -0,0 +1,164 @@
+import { useQuery, useQueryClient } from '@tanstack/react-query'
+import { useState, useEffect, useCallback } from 'react'
+import { useNostr } from '@/hooks/useNostr'
+
+const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY
+
+export function useNostrQueries() {
+ const [isClient, setIsClient] = useState(false)
+
+ const { subscribe, fetchZapsForEvent, fetchZapsForEvents } = useNostr()
+ const queryClient = useQueryClient()
+
+ useEffect(() => {
+ setIsClient(true)
+ }, [])
+
+ const fetchWorkshops = async () => {
+ const filter = [{ kinds: [30023, 30402], authors: [AUTHOR_PUBKEY] }]
+ const hasRequiredTags = (tags) => {
+ const hasPlebDevs = tags.some(([tag, value]) => tag === "t" && value === "plebdevs")
+ const hasWorkshop = tags.some(([tag, value]) => tag === "t" && value === "workshop")
+ return hasPlebDevs && hasWorkshop
+ }
+
+ return new Promise((resolve) => {
+ let workshops = []
+ const subscription = subscribe(filter,
+ {
+ onevent: (event) => {
+ if (hasRequiredTags(event.tags)) {
+ workshops.push(event)
+ }
+ },
+ onerror: (error) => {
+ console.error('Error fetching workshops:', error)
+ reject(error);
+ },
+ onclose: () => {
+ resolve(workshops)
+ },
+ }
+ )
+
+ // Set a timeout to resolve the promise after collecting events
+ setTimeout(() => {
+ subscription?.close()
+ resolve(workshops)
+ }, 2000) // Adjust the timeout value as needed
+ })
+ }
+
+ const fetchResources = async () => {
+ console.log('fetching resources');
+ const filter = [{ kinds: [30023, 30402], authors: [AUTHOR_PUBKEY] }];
+ const hasRequiredTags = (tags) => {
+ const hasPlebDevs = tags.some(([tag, value]) => tag === "t" && value === "plebdevs");
+ const hasResource = tags.some(([tag, value]) => tag === "t" && value === "resource");
+ return hasPlebDevs && hasResource;
+ };
+
+ return new Promise((resolve, reject) => {
+ let resources = [];
+ const subscription = subscribe(
+ filter,
+ {
+ onevent: (event) => {
+ if (hasRequiredTags(event.tags)) {
+ resources.push(event);
+ }
+ },
+ onerror: (error) => {
+ console.error('Error fetching resources:', error);
+ reject(error);
+ },
+ onclose: () => {
+ resolve(resources);
+ },
+ }
+ );
+
+ // Set a timeout to resolve the promise after collecting events
+ setTimeout(() => {
+ subscription?.close();
+ resolve(resources);
+ }, 2000); // Adjust the timeout value as needed
+ });
+ }
+
+ const fetchCourses = async () => {
+ const filter = [{ kinds: [30004], authors: [AUTHOR_PUBKEY] }];
+ // Do we need required tags for courses? community instead?
+ // const hasRequiredTags = (tags) => {
+ // const hasPlebDevs = tags.some(([tag, value]) => tag === "t" && value === "plebdevs");
+ // const hasCourse = tags.some(([tag, value]) => tag === "t" && value === "course");
+ // return hasPlebDevs && hasCourse;
+ // };
+
+ return new Promise((resolve, reject) => {
+ let courses = [];
+ const subscription = subscribe(
+ filter,
+ {
+ onevent: (event) => {
+ // if (hasRequiredTags(event.tags)) {
+ // courses.push(event);
+ // }
+ courses.push(event);
+ },
+ onerror: (error) => {
+ console.error('Error fetching courses:', error);
+ reject(error);
+ },
+ onclose: () => {
+ resolve(courses);
+ },
+ }
+ );
+
+ setTimeout(() => {
+ subscription?.close();
+ resolve(courses);
+ }, 2000);
+ });
+ }
+
+ const { data: workshops, isLoading: workshopsLoading, error: workshopsError, refetch: refetchWorkshops } = useQuery({
+ queryKey: ['workshops', isClient],
+ queryFn: fetchWorkshops,
+ staleTime: 1000 * 60 * 10, // 10 minutes
+ cacheTime: 1000 * 60 * 60, // 1 hour
+ enabled: isClient,
+ })
+
+ const { data: resources, isLoading: resourcesLoading, error: resourcesError, refetch: refetchResources } = useQuery({
+ queryKey: ['resources', isClient],
+ queryFn: fetchResources,
+ staleTime: 1000 * 60 * 10, // 10 minutes
+ cacheTime: 1000 * 60 * 60, // 1 hour
+ enabled: isClient,
+ })
+
+ const { data: courses, isLoading: coursesLoading, error: coursesError, refetch: refetchCourses } = useQuery({
+ queryKey: ['courses', isClient],
+ queryFn: fetchCourses,
+ staleTime: 1000 * 60 * 10, // 10 minutes
+ cacheTime: 1000 * 60 * 60, // 1 hour
+ enabled: isClient,
+ })
+
+ return {
+ workshops,
+ workshopsLoading,
+ workshopsError,
+ resources,
+ resourcesLoading,
+ resourcesError,
+ courses,
+ coursesLoading,
+ coursesError,
+ refetchCourses,
+ refetchResources,
+ refetchWorkshops,
+ }
+}
\ No newline at end of file
diff --git a/src/pages/_app.js b/src/pages/_app.js
index ce3e9df..ae98e4d 100644
--- a/src/pages/_app.js
+++ b/src/pages/_app.js
@@ -9,28 +9,35 @@ import "@uiw/react-md-editor/markdown-editor.css";
import "@uiw/react-markdown-preview/markdown.css";
import Sidebar from '@/components/sidebar/Sidebar';
import { NostrProvider } from '@/context/NostrContext';
+import {
+ QueryClient,
+ QueryClientProvider,
+} from '@tanstack/react-query'
+
+const queryClient = new QueryClient()
export default function MyApp({
Component, pageProps: { ...pageProps }
}) {
-
return (
-
-
-
-
- {/*
*/}
- {/*
*/}
- {/*
*/}
-
-
-
+
+
+
);
diff --git a/src/pages/draft/[slug]/index.js b/src/pages/draft/[slug]/index.js
index aea81b5..c5077d2 100644
--- a/src/pages/draft/[slug]/index.js
+++ b/src/pages/draft/[slug]/index.js
@@ -124,12 +124,11 @@ export default function Details() {
const payload = {
id: dTag,
userId: userResponse.data.id,
- price: draft.price || 0,
+ price: Number(draft.price) || 0,
noteId: nAddress,
}
- console.log('payload:', payload);
+
const response = await axios.post(`/api/resources`, payload);
- console.log('response:', response);
if (response.status !== 201) {
showToast('error', 'Error', 'Failed to create resource. Please try again.');
@@ -145,19 +144,14 @@ export default function Details() {
published = await publishCourse(signedEvent);
}
- console.log('published:', published);
-
if (published) {
// check if the event is published
const publishedEvent = await fetchSingleEvent(signedEvent.id);
- console.log('publishedEvent:', publishedEvent);
-
if (publishedEvent) {
// show success message
showToast('success', 'Success', `${type} published successfully.`);
// delete the draft
- console.log('draft:', draft);
await axios.delete(`/api/drafts/${draft.id}`)
.then(res => {
if (res.status === 204) {
@@ -198,7 +192,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/details/${draft.id}`]] : []),
+ ...(draft?.price ? [['price', draft.price.toString()], ['location', `https://plebdevs.com/details/${draft.id}`]] : []),
]
};
type = 'resource';
diff --git a/src/pages/index.js b/src/pages/index.js
index 9078075..76fa4ca 100644
--- a/src/pages/index.js
+++ b/src/pages/index.js
@@ -8,21 +8,6 @@ import { useLocalStorageWithEffect } from '@/hooks/useLocalStorage';
import axios from 'axios';
export default function Home() {
- const [contentIds, setContentIds] = useLocalStorageWithEffect('contentIds', []);
-
- const fetchContentIds = useCallback(async () => {
- try {
- const response = await axios.get('/api/content/all');
- const ids = response.data;
- setContentIds(ids);
- } catch (error) {
- console.error('Failed to fetch content IDs:', error);
- }
- }, []);
-
- useEffect(() => {
- fetchContentIds();
- }, [fetchContentIds]);
return (
<>
@@ -34,9 +19,9 @@ export default function Home() {
-
+ {/* */}
-
+ {/* */}
>
);