Fixes to content fetching based on correct tags and presence of id in db, fixed forms

This commit is contained in:
austinkelsay 2024-03-27 14:44:54 -05:00
parent a06542c4a3
commit 6eb4edf617
13 changed files with 183 additions and 103 deletions

View File

@ -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 (
<>
<h2 className="ml-[6%] mt-4">resources</h2>
<Carousel value={[...processedResources, ...processedResources]} numVisible={2} itemTemplate={ResourceTemplate} responsiveOptions={responsiveOptions} />
</>
);
}

View File

@ -26,7 +26,6 @@ const responsiveOptions = [
export default function WorkshopsCarousel() { export default function WorkshopsCarousel() {
const [processedWorkshops, setProcessedWorkshops] = useState([]); const [processedWorkshops, setProcessedWorkshops] = useState([]);
const [screenWidth, setScreenWidth] = useState(null);
const [workshops, setWorkshops] = useState([]); const [workshops, setWorkshops] = useState([]);
const router = useRouter(); const router = useRouter();
const { fetchWorkshops, events } = useNostr(); const { fetchWorkshops, events } = useNostr();
@ -40,35 +39,6 @@ export default function WorkshopsCarousel() {
} }
}, [events]); }, [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(() => { useEffect(() => {
const processWorkshops = workshops.map(workshop => { const processWorkshops = workshops.map(workshop => {
const { id, content, title, summary, image, published_at } = parseEvent(workshop); const { id, content, title, summary, image, published_at } = parseEvent(workshop);

View File

@ -28,7 +28,7 @@ const CourseForm = () => {
price: checked ? price : null, price: checked ? price : null,
content: text, content: text,
topics: topics.map(topic => topic.trim().toLowerCase()), 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); console.log(payload);

View File

@ -21,7 +21,7 @@ const ResourceForm = () => {
const [price, setPrice] = useState(0); const [price, setPrice] = useState(0);
const [text, setText] = useState(''); const [text, setText] = useState('');
const [coverImage, setCoverImage] = useState(''); const [coverImage, setCoverImage] = useState('');
const [topics, setTopics] = useState(['']); const [topics, setTopics] = useState([]);
const [user] = useLocalStorageWithEffect('user', {}); const [user] = useLocalStorageWithEffect('user', {});
@ -49,7 +49,7 @@ const ResourceForm = () => {
content: text, content: text,
image: coverImage, image: coverImage,
user: userResponse.data.id, user: userResponse.data.id,
topics: topics.map(topic => topic.trim().toLowerCase()) topics: [...topics.map(topic => topic.trim().toLowerCase()), 'plebdevs', 'resource']
}; };
if (payload && payload.user) { if (payload && payload.user) {

View File

@ -49,7 +49,7 @@ const WorkshopForm = () => {
isPaidResource: checked, isPaidResource: checked,
price: checked ? price : null, price: checked ? price : null,
embedCode, 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); console.log(payload);

View File

@ -33,9 +33,11 @@ const UserContent = () => {
const fetchAllContent = async () => { const fetchAllContent = async () => {
try { try {
console.log(user.id)
// Fetch drafts from the database // Fetch drafts from the database
const draftsResponse = await axios.get(`/api/drafts/all/${user.id}`); const draftsResponse = await axios.get(`/api/drafts/all/${user.id}`);
const drafts = draftsResponse.data; const drafts = draftsResponse.data;
console.log('drafts:', drafts);
// Fetch resources, workshops, and courses from Nostr // Fetch resources, workshops, and courses from Nostr
fetchResources(); fetchResources();

View File

@ -1,5 +1,6 @@
import { useState, useEffect, useRef } from "react"; import { useState, useEffect, useRef } from "react";
import { SimplePool, nip19, verifyEvent } from "nostr-tools"; import { SimplePool, nip19, verifyEvent } from "nostr-tools";
import axios from "axios";
import { useToast } from "./useToast"; import { useToast } from "./useToast";
const initialRelays = [ const initialRelays = [
@ -22,7 +23,7 @@ export const useNostr = () => {
streams: [] streams: []
}); });
const {showToast} = useToast(); const { showToast } = useToast();
const pool = useRef(new SimplePool({ seenOnEnabled: true })); const pool = useRef(new SimplePool({ seenOnEnabled: true }));
const subscriptions = useRef([]); const subscriptions = useRef([]);
@ -51,8 +52,9 @@ export const useNostr = () => {
const fetchEvents = async (filter, updateDataField, hasRequiredTags) => { const fetchEvents = async (filter, updateDataField, hasRequiredTags) => {
try { try {
const sub = pool.current.subscribeMany(relays, filter, { const sub = pool.current.subscribeMany(relays, filter, {
onevent: (event) => { onevent: async (event) => {
if (hasRequiredTags(event.tags)) { const shouldInclude = await hasRequiredTags(event.tags);
if (shouldInclude) {
setEvents(prevData => ({ setEvents(prevData => ({
...prevData, ...prevData,
[updateDataField]: [...prevData[updateDataField], event] [updateDataField]: [...prevData[updateDataField], event]
@ -74,29 +76,78 @@ export const useNostr = () => {
}; };
// Fetch resources, workshops, courses, and streams with appropriate filters and update functions // Fetch resources, workshops, courses, and streams with appropriate filters and update functions
const fetchResources = () => { const fetchResources = async () => {
const filter = [{kinds: [30023], authors: ["f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741"]}]; 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 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); fetchEvents(filter, 'resources', hasRequiredTags);
}; };
const fetchWorkshops = () => { const fetchWorkshops = async () => {
const filter = [{kinds: [30023], authors: ["f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741"]}]; 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 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); fetchEvents(filter, 'workshops', hasRequiredTags);
} };
const fetchCourses = () => { const fetchCourses = async () => {
const filter = [{kinds: [30023], authors: ["f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741"]}]; 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 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); fetchEvents(filter, 'courses', hasRequiredTags);
} };
const fetchStreams = () => { // const fetchStreams = () => {
const filter = [{kinds: [30311], authors: ["f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741"]}]; // const filter = [{kinds: [30311], authors: ["f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741"]}];
const hasRequiredTags = (eventData) => eventData.some(([tag, value]) => tag === "t" && value === "plebdevs"); // const hasRequiredTags = (eventData) => eventData.some(([tag, value]) => tag === "t" && value === "plebdevs");
fetchEvents(filter, 'streams', hasRequiredTags); // fetchEvents(filter, 'streams', hasRequiredTags);
} // }
const fetchKind0 = async (criteria, params) => { const fetchKind0 = async (criteria, params) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -151,7 +202,7 @@ export const useNostr = () => {
let timer let timer
let isMessageSentSuccessfully = false let isMessageSentSuccessfully = false
function timedout () { function timedout() {
clearTimeout(timer) clearTimeout(timer)
wsRelay.close() wsRelay.close()
reject(new Error(`relay timeout for ${relay}`)) reject(new Error(`relay timeout for ${relay}`))
@ -236,7 +287,7 @@ export const useNostr = () => {
fetchResources, fetchResources,
fetchCourses, fetchCourses,
fetchWorkshops, fetchWorkshops,
fetchStreams, // fetchStreams,
getRelayStatuses, getRelayStatuses,
events events
}; };

View File

@ -1,11 +1,11 @@
import { getCourseById, updateCourse, deleteCourse } from "@/db/models/courseModels"; import { getCourseById, updateCourse, deleteCourse } from "@/db/models/courseModels";
export default async function handler(req, res) { export default async function handler(req, res) {
const { id } = req.query; const { slug } = req.query;
if (req.method === 'GET') { if (req.method === 'GET') {
try { try {
const course = await getCourseById(parseInt(id)); const course = await getCourseById(slug);
if (course) { if (course) {
res.status(200).json(course); res.status(200).json(course);
} else { } else {
@ -16,14 +16,14 @@ export default async function handler(req, res) {
} }
} else if (req.method === 'PUT') { } else if (req.method === 'PUT') {
try { try {
const course = await updateCourse(parseInt(id), req.body); const course = await updateCourse(slug, req.body);
res.status(200).json(course); res.status(200).json(course);
} catch (error) { } catch (error) {
res.status(400).json({ error: error.message }); res.status(400).json({ error: error.message });
} }
} else if (req.method === 'DELETE') { } else if (req.method === 'DELETE') {
try { try {
await deleteCourse(parseInt(id)); await deleteCourse(slug);
res.status(204).end(); res.status(204).end();
} catch (error) { } catch (error) {
res.status(500).json({ error: error.message }); res.status(500).json({ error: error.message });

View File

@ -1,15 +1,15 @@
import { getAllDraftsByUserId } from "@/db/models/draftModels"; import { getAllDraftsByUserId } from "@/db/models/draftModels";
export default async function handler(req, res) { export default async function handler(req, res) {
const { id } = req.query; const { slug } = req.query;
if (req.method === 'GET') { if (req.method === 'GET') {
try { try {
const resource = await getAllDraftsByUserId(id); const drafts = await getAllDraftsByUserId(slug);
if (resource) { if (drafts) {
res.status(200).json(resource); res.status(200).json(drafts);
} else { } else {
res.status(404).json({ error: 'Resource not found' }); res.status(404).json({ error: 'Drafts not found' });
} }
} catch (error) { } catch (error) {
res.status(400).json({ error: error.message }); res.status(400).json({ error: error.message });

View File

@ -1,11 +1,11 @@
import { getResourceById, updateResource, deleteResource } from "@/db/models/resourceModels"; import { getResourceById, updateResource, deleteResource } from "@/db/models/resourceModels";
export default async function handler(req, res) { export default async function handler(req, res) {
const { id } = req.query; const { slug } = req.query;
if (req.method === 'GET') { if (req.method === 'GET') {
try { try {
const resource = await getResourceById(parseInt(id)); const resource = await getResourceById(slug);
if (resource) { if (resource) {
res.status(200).json(resource); res.status(200).json(resource);
} else { } else {
@ -16,14 +16,14 @@ export default async function handler(req, res) {
} }
} else if (req.method === 'PUT') { } else if (req.method === 'PUT') {
try { try {
const resource = await updateResource(parseInt(id), req.body); const resource = await updateResource(slug, req.body);
res.status(200).json(resource); res.status(200).json(resource);
} catch (error) { } catch (error) {
res.status(400).json({ error: error.message }); res.status(400).json({ error: error.message });
} }
} else if (req.method === 'DELETE') { } else if (req.method === 'DELETE') {
try { try {
await deleteResource(parseInt(id)); await deleteResource(slug);
res.status(204).end(); res.status(204).end();
} catch (error) { } catch (error) {
res.status(500).json({ error: error.message }); res.status(500).json({ error: error.message });

View File

@ -27,7 +27,7 @@ const Create = () => {
}; };
return ( return (
<div className="w-fit mx-auto my-8 flex flex-col justify-center"> <div className="w-[80vw] max-w-[80vw] mx-auto my-8 flex flex-col justify-center">
<h2 className="text-center mb-8">Create a {homeItems[activeIndex].label}</h2> <h2 className="text-center mb-8">Create a {homeItems[activeIndex].label}</h2>
<MenuTab items={homeItems} activeIndex={activeIndex} onTabChange={setActiveIndex} /> <MenuTab items={homeItems} activeIndex={activeIndex} onTabChange={setActiveIndex} />
{renderForm()} {renderForm()}

View File

@ -50,7 +50,6 @@ export default function Details() {
const fetchAuthor = async (pubkey) => { const fetchAuthor = async (pubkey) => {
const author = await fetchKind0([{ authors: [pubkey], kinds: [0] }], {}); const author = await fetchKind0([{ authors: [pubkey], kinds: [0] }], {});
const fields = await findKind0Fields(author); const fields = await findKind0Fields(author);
console.log('fields:', fields);
if (fields) { if (fields) {
setAuthor(fields); setAuthor(fields);
} }

View File

@ -3,6 +3,7 @@ import React from 'react';
import CoursesCarousel from '@/components/content/carousels/CoursesCarousel' import CoursesCarousel from '@/components/content/carousels/CoursesCarousel'
import WorkshopsCarousel from '@/components/content/carousels/WorkshopsCarousel' import WorkshopsCarousel from '@/components/content/carousels/WorkshopsCarousel'
import HeroBanner from '@/components/banner/HeroBanner'; import HeroBanner from '@/components/banner/HeroBanner';
import ResourcesCarousel from '@/components/content/carousels/ResourcesCarousel';
export default function Home() { export default function Home() {
return ( return (
@ -17,6 +18,7 @@ export default function Home() {
<HeroBanner /> <HeroBanner />
<CoursesCarousel /> <CoursesCarousel />
<WorkshopsCarousel /> <WorkshopsCarousel />
<ResourcesCarousel />
</main> </main>
</> </>
) )