From 6eb4edf6179a8c0f9054b7c6607416d4d1eef702 Mon Sep 17 00:00:00 2001 From: austinkelsay Date: Wed, 27 Mar 2024 14:44:54 -0500 Subject: [PATCH] Fixes to content fetching based on correct tags and presence of id in db, fixed forms --- .../content/carousels/ResourcesCarousel.js | 56 ++++++ .../content/carousels/WorkshopsCarousel.js | 30 ---- src/components/forms/CourseForm.js | 2 +- src/components/forms/ResourceForm.js | 4 +- src/components/forms/WorkshopForm.js | 2 +- src/components/profile/UserContent.js | 2 + src/hooks/useNostr.js | 159 ++++++++++++------ src/pages/api/courses/[slug].js | 8 +- src/pages/api/drafts/all/[slug].js | 10 +- src/pages/api/resources/[slug].js | 8 +- src/pages/create.js | 2 +- src/pages/details/[slug].js | 1 - src/pages/index.js | 2 + 13 files changed, 183 insertions(+), 103 deletions(-) create mode 100644 src/components/content/carousels/ResourcesCarousel.js diff --git a/src/components/content/carousels/ResourcesCarousel.js b/src/components/content/carousels/ResourcesCarousel.js new file mode 100644 index 0000000..7a07e35 --- /dev/null +++ b/src/components/content/carousels/ResourcesCarousel.js @@ -0,0 +1,56 @@ +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 { parseEvent } from '@/utils/nostr'; +import ResourceTemplate from '@/components/content/carousels/templates/ResourceTemplate'; + +const responsiveOptions = [ + { + breakpoint: '3000px', + numVisible: 3, + numScroll: 1 + }, + { + breakpoint: '1462px', + numVisible: 2, + numScroll: 1 + }, + { + breakpoint: '575px', + numVisible: 1, + numScroll: 1 + } +]; + +export default function ResourcesCarousel() { + const [processedResources, setProcessedResources] = useState([]); + const [resources, setResources] = useState([]); + const router = useRouter(); + const { fetchResources, events } = useNostr(); + const { returnImageProxy } = useImageProxy(); + + useEffect(() => { + if (events && events.resources && events.resources.length > 0) { + setResources(events.resources); + } else { + fetchResources(); + } + }, [events]); + + useEffect(() => { + const processResources = resources.map(resource => { + const { id, content, title, summary, image, published_at } = parseEvent(resource); + return { id, content, title, summary, image, published_at }; + }); + setProcessedResources(processResources); + }, [resources]); + + return ( + <> +

resources

+ + + ); +} diff --git a/src/components/content/carousels/WorkshopsCarousel.js b/src/components/content/carousels/WorkshopsCarousel.js index 275514d..11cc4fe 100644 --- a/src/components/content/carousels/WorkshopsCarousel.js +++ b/src/components/content/carousels/WorkshopsCarousel.js @@ -26,7 +26,6 @@ const responsiveOptions = [ export default function WorkshopsCarousel() { const [processedWorkshops, setProcessedWorkshops] = useState([]); - const [screenWidth, setScreenWidth] = useState(null); const [workshops, setWorkshops] = useState([]); const router = useRouter(); const { fetchWorkshops, events } = useNostr(); @@ -40,35 +39,6 @@ export default function WorkshopsCarousel() { } }, [events]); - useEffect(() => { - // Update the state to the current window width - setScreenWidth(window.innerWidth); - - const handleResize = () => { - // Update the state to the new window width when it changes - setScreenWidth(window.innerWidth); - }; - - window.addEventListener('resize', handleResize); - - // Remove the event listener on cleanup - return () => window.removeEventListener('resize', handleResize); - }, []); // The empty array ensures this effect only runs once, similar to componentDidMount - - - const calculateImageDimensions = () => { - if (screenWidth >= 1200) { - // Large screens - return { width: 426, height: 240 }; - } else if (screenWidth >= 768 && screenWidth < 1200) { - // Medium screens - return { width: 344, height: 194 }; - } else { - // Small screens - return { width: screenWidth - 120, height: (screenWidth - 120) * (9 / 16) }; - } - }; - useEffect(() => { const processWorkshops = workshops.map(workshop => { const { id, content, title, summary, image, published_at } = parseEvent(workshop); diff --git a/src/components/forms/CourseForm.js b/src/components/forms/CourseForm.js index e357adb..b240273 100644 --- a/src/components/forms/CourseForm.js +++ b/src/components/forms/CourseForm.js @@ -28,7 +28,7 @@ const CourseForm = () => { price: checked ? price : null, content: text, topics: topics.map(topic => topic.trim().toLowerCase()), - resources: resources.map(resource => resource.trim()) + topics: [...topics.map(topic => topic.trim().toLowerCase()), 'plebdevs', 'course'] }; console.log(payload); diff --git a/src/components/forms/ResourceForm.js b/src/components/forms/ResourceForm.js index 6889445..fda5a0f 100644 --- a/src/components/forms/ResourceForm.js +++ b/src/components/forms/ResourceForm.js @@ -21,7 +21,7 @@ const ResourceForm = () => { const [price, setPrice] = useState(0); const [text, setText] = useState(''); const [coverImage, setCoverImage] = useState(''); - const [topics, setTopics] = useState(['']); + const [topics, setTopics] = useState([]); const [user] = useLocalStorageWithEffect('user', {}); @@ -49,7 +49,7 @@ const ResourceForm = () => { content: text, image: coverImage, user: userResponse.data.id, - topics: topics.map(topic => topic.trim().toLowerCase()) + topics: [...topics.map(topic => topic.trim().toLowerCase()), 'plebdevs', 'resource'] }; if (payload && payload.user) { diff --git a/src/components/forms/WorkshopForm.js b/src/components/forms/WorkshopForm.js index 8ee11e2..3d55969 100644 --- a/src/components/forms/WorkshopForm.js +++ b/src/components/forms/WorkshopForm.js @@ -49,7 +49,7 @@ const WorkshopForm = () => { isPaidResource: checked, price: checked ? price : null, embedCode, - topics: topics.map(topic => topic.trim().toLowerCase()) // Include topics in the payload + topics: [...topics.map(topic => topic.trim().toLowerCase()), 'plebdevs', 'workshop'] }; console.log(payload); diff --git a/src/components/profile/UserContent.js b/src/components/profile/UserContent.js index 7841cf2..c9327fe 100644 --- a/src/components/profile/UserContent.js +++ b/src/components/profile/UserContent.js @@ -33,9 +33,11 @@ const UserContent = () => { const fetchAllContent = async () => { try { + console.log(user.id) // Fetch drafts from the database const draftsResponse = await axios.get(`/api/drafts/all/${user.id}`); const drafts = draftsResponse.data; + console.log('drafts:', drafts); // Fetch resources, workshops, and courses from Nostr fetchResources(); diff --git a/src/hooks/useNostr.js b/src/hooks/useNostr.js index ce84462..7d6280e 100644 --- a/src/hooks/useNostr.js +++ b/src/hooks/useNostr.js @@ -1,5 +1,6 @@ import { useState, useEffect, useRef } from "react"; import { SimplePool, nip19, verifyEvent } from "nostr-tools"; +import axios from "axios"; import { useToast } from "./useToast"; const initialRelays = [ @@ -22,7 +23,7 @@ export const useNostr = () => { streams: [] }); - const {showToast} = useToast(); + const { showToast } = useToast(); const pool = useRef(new SimplePool({ seenOnEnabled: true })); const subscriptions = useRef([]); @@ -51,8 +52,9 @@ export const useNostr = () => { const fetchEvents = async (filter, updateDataField, hasRequiredTags) => { try { const sub = pool.current.subscribeMany(relays, filter, { - onevent: (event) => { - if (hasRequiredTags(event.tags)) { + onevent: async (event) => { + const shouldInclude = await hasRequiredTags(event.tags); + if (shouldInclude) { setEvents(prevData => ({ ...prevData, [updateDataField]: [...prevData[updateDataField], event] @@ -74,29 +76,78 @@ export const useNostr = () => { }; // Fetch resources, workshops, courses, and streams with appropriate filters and update functions - const fetchResources = () => { - const filter = [{kinds: [30023], authors: ["f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741"]}]; - const hasRequiredTags = (eventData) => eventData.some(([tag, value]) => tag === "t" && value === "plebdevs") && eventData.some(([tag, value]) => tag === "t" && value === "resource"); + const fetchResources = async () => { + const filter = [{ kinds: [30023], authors: ["f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741"] }]; + 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 = () => { - const filter = [{kinds: [30023], authors: ["f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741"]}]; - const hasRequiredTags = (eventData) => eventData.some(([tag, value]) => tag === "t" && value === "plebdevs") && eventData.some(([tag, value]) => tag === "t" && value === "resource"); + const fetchWorkshops = async () => { + const filter = [{ kinds: [30023], authors: ["f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741"] }]; + 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 = () => { - const filter = [{kinds: [30023], authors: ["f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741"]}]; - const hasRequiredTags = (eventData) => eventData.some(([tag, value]) => tag === "t" && value === "plebdevs") && eventData.some(([tag, value]) => tag === "t" && value === "course"); + const fetchCourses = async () => { + const filter = [{ kinds: [30023], authors: ["f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741"] }]; + 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: ["f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741"]}]; - const hasRequiredTags = (eventData) => eventData.some(([tag, value]) => tag === "t" && value === "plebdevs"); - fetchEvents(filter, 'streams', hasRequiredTags); - } + // const fetchStreams = () => { + // const filter = [{kinds: [30311], authors: ["f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741"]}]; + // 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) => { @@ -150,45 +201,45 @@ export const useNostr = () => { 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 + + function timedout() { clearTimeout(timer) wsRelay.close() - console.log('Successfully sent event to', relay) - resolve() - } + 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) { + 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}`)) + } + } + }) }; @@ -214,7 +265,7 @@ export const useNostr = () => { console.error('Error publishing event:', error); } }; - + useEffect(() => { getRelayStatuses(); // Get initial statuses on mount @@ -236,7 +287,7 @@ export const useNostr = () => { fetchResources, fetchCourses, fetchWorkshops, - fetchStreams, + // fetchStreams, getRelayStatuses, events }; diff --git a/src/pages/api/courses/[slug].js b/src/pages/api/courses/[slug].js index 8b9669d..b6aa293 100644 --- a/src/pages/api/courses/[slug].js +++ b/src/pages/api/courses/[slug].js @@ -1,11 +1,11 @@ import { getCourseById, updateCourse, deleteCourse } from "@/db/models/courseModels"; export default async function handler(req, res) { - const { id } = req.query; + const { slug } = req.query; if (req.method === 'GET') { try { - const course = await getCourseById(parseInt(id)); + const course = await getCourseById(slug); if (course) { res.status(200).json(course); } else { @@ -16,14 +16,14 @@ export default async function handler(req, res) { } } else if (req.method === 'PUT') { try { - const course = await updateCourse(parseInt(id), req.body); + const course = await updateCourse(slug, req.body); res.status(200).json(course); } catch (error) { res.status(400).json({ error: error.message }); } } else if (req.method === 'DELETE') { try { - await deleteCourse(parseInt(id)); + await deleteCourse(slug); res.status(204).end(); } catch (error) { res.status(500).json({ error: error.message }); diff --git a/src/pages/api/drafts/all/[slug].js b/src/pages/api/drafts/all/[slug].js index 8be5921..fab2c85 100644 --- a/src/pages/api/drafts/all/[slug].js +++ b/src/pages/api/drafts/all/[slug].js @@ -1,15 +1,15 @@ import { getAllDraftsByUserId } from "@/db/models/draftModels"; export default async function handler(req, res) { -const { id } = req.query; +const { slug } = req.query; if (req.method === 'GET') { try { - const resource = await getAllDraftsByUserId(id); - if (resource) { - res.status(200).json(resource); + const drafts = await getAllDraftsByUserId(slug); + if (drafts) { + res.status(200).json(drafts); } else { - res.status(404).json({ error: 'Resource not found' }); + res.status(404).json({ error: 'Drafts not found' }); } } catch (error) { res.status(400).json({ error: error.message }); diff --git a/src/pages/api/resources/[slug].js b/src/pages/api/resources/[slug].js index e0c26b5..e8f4d66 100644 --- a/src/pages/api/resources/[slug].js +++ b/src/pages/api/resources/[slug].js @@ -1,11 +1,11 @@ import { getResourceById, updateResource, deleteResource } from "@/db/models/resourceModels"; export default async function handler(req, res) { - const { id } = req.query; + const { slug } = req.query; if (req.method === 'GET') { try { - const resource = await getResourceById(parseInt(id)); + const resource = await getResourceById(slug); if (resource) { res.status(200).json(resource); } else { @@ -16,14 +16,14 @@ export default async function handler(req, res) { } } else if (req.method === 'PUT') { try { - const resource = await updateResource(parseInt(id), req.body); + const resource = await updateResource(slug, req.body); res.status(200).json(resource); } catch (error) { res.status(400).json({ error: error.message }); } } else if (req.method === 'DELETE') { try { - await deleteResource(parseInt(id)); + await deleteResource(slug); res.status(204).end(); } catch (error) { res.status(500).json({ error: error.message }); diff --git a/src/pages/create.js b/src/pages/create.js index bd79f94..60712e6 100644 --- a/src/pages/create.js +++ b/src/pages/create.js @@ -27,7 +27,7 @@ const Create = () => { }; return ( -
+

Create a {homeItems[activeIndex].label}

{renderForm()} diff --git a/src/pages/details/[slug].js b/src/pages/details/[slug].js index d083a91..26e2a33 100644 --- a/src/pages/details/[slug].js +++ b/src/pages/details/[slug].js @@ -50,7 +50,6 @@ export default function Details() { const fetchAuthor = async (pubkey) => { const author = await fetchKind0([{ authors: [pubkey], kinds: [0] }], {}); const fields = await findKind0Fields(author); - console.log('fields:', fields); if (fields) { setAuthor(fields); } diff --git a/src/pages/index.js b/src/pages/index.js index 2a74729..782ed73 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -3,6 +3,7 @@ import React from 'react'; import CoursesCarousel from '@/components/content/carousels/CoursesCarousel' import WorkshopsCarousel from '@/components/content/carousels/WorkshopsCarousel' import HeroBanner from '@/components/banner/HeroBanner'; +import ResourcesCarousel from '@/components/content/carousels/ResourcesCarousel'; export default function Home() { return ( @@ -17,6 +18,7 @@ export default function Home() { + )