mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-06 18:31:00 +00:00
Fix loading states for carousels and zaps, reorganized nostrQueries, added zap subscription hook for details
This commit is contained in:
parent
c5eb71e4b8
commit
e2181722e3
@ -3,7 +3,7 @@ import { Carousel } from 'primereact/carousel';
|
|||||||
import { parseCourseEvent } from '@/utils/nostr';
|
import { parseCourseEvent } from '@/utils/nostr';
|
||||||
import CourseTemplate from '@/components/content/carousels/templates/CourseTemplate';
|
import CourseTemplate from '@/components/content/carousels/templates/CourseTemplate';
|
||||||
import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton';
|
import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton';
|
||||||
import { useCoursesQuery } from '@/hooks/nostrQueries/useCoursesQuery';
|
import { useCoursesQuery } from '@/hooks/nostrQueries/content/useCoursesQuery';
|
||||||
|
|
||||||
const responsiveOptions = [
|
const responsiveOptions = [
|
||||||
{
|
{
|
||||||
@ -48,21 +48,17 @@ export default function CoursesCarousel() {
|
|||||||
return <div>Error: {coursesError.message}</div>
|
return <div>Error: {coursesError.message}</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (coursesLoading) {
|
|
||||||
return <div>Loading...</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h2 className="ml-[6%] mt-4">Courses</h2>
|
<h2 className="ml-[6%] mt-4">Courses</h2>
|
||||||
<div className={"min-h-[384px]"}>
|
<div className={"min-h-[384px]"}>
|
||||||
<Carousel
|
<Carousel
|
||||||
value={!processedCourses.length > 0 ? [{}, {}, {}] : [...processedCourses]}
|
value={coursesLoading || !processedCourses.length ? [{}, {}, {}] : [...processedCourses]}
|
||||||
numVisible={2}
|
numVisible={2}
|
||||||
itemTemplate={(item) =>
|
itemTemplate={(item) =>
|
||||||
processedCourses.length > 0 ?
|
!processedCourses.length ?
|
||||||
<CourseTemplate key={item.id} course={item} /> :
|
<TemplateSkeleton key={Math.random()} /> :
|
||||||
<TemplateSkeleton key={Math.random()} />
|
<CourseTemplate key={item.id} course={item} />
|
||||||
}
|
}
|
||||||
responsiveOptions={responsiveOptions} />
|
responsiveOptions={responsiveOptions} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,7 @@ import { Carousel } from 'primereact/carousel';
|
|||||||
import { parseEvent } from '@/utils/nostr';
|
import { parseEvent } from '@/utils/nostr';
|
||||||
import ResourceTemplate from '@/components/content/carousels/templates/ResourceTemplate';
|
import ResourceTemplate from '@/components/content/carousels/templates/ResourceTemplate';
|
||||||
import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton';
|
import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton';
|
||||||
import { useResourcesQuery } from '@/hooks/nostrQueries/useResourcesQuery';
|
import { useResourcesQuery } from '@/hooks/nostrQueries/content/useResourcesQuery';
|
||||||
|
|
||||||
const responsiveOptions = [
|
const responsiveOptions = [
|
||||||
{
|
{
|
||||||
@ -42,10 +42,6 @@ export default function ResourcesCarousel() {
|
|||||||
fetch();
|
fetch();
|
||||||
}, [resources]);
|
}, [resources]);
|
||||||
|
|
||||||
if (resourcesLoading) {
|
|
||||||
return <div>Loading...</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resourcesError) {
|
if (resourcesError) {
|
||||||
return <div>Error: {resourcesError.message}</div>
|
return <div>Error: {resourcesError.message}</div>
|
||||||
}
|
}
|
||||||
@ -53,7 +49,8 @@ export default function ResourcesCarousel() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h2 className="ml-[6%] mt-4">Resources</h2>
|
<h2 className="ml-[6%] mt-4">Resources</h2>
|
||||||
<Carousel value={!processedResources.length > 0 ? [{}, {}, {}] : [...processedResources]}
|
<Carousel
|
||||||
|
value={resourcesLoading || !processedResources.length ? [{}, {}, {}] : [...processedResources]}
|
||||||
numVisible={2}
|
numVisible={2}
|
||||||
itemTemplate={(item) =>
|
itemTemplate={(item) =>
|
||||||
processedResources.length > 0 ?
|
processedResources.length > 0 ?
|
||||||
|
@ -3,7 +3,7 @@ import { Carousel } from 'primereact/carousel';
|
|||||||
import { parseEvent } from '@/utils/nostr';
|
import { parseEvent } from '@/utils/nostr';
|
||||||
import WorkshopTemplate from '@/components/content/carousels/templates/WorkshopTemplate';
|
import WorkshopTemplate from '@/components/content/carousels/templates/WorkshopTemplate';
|
||||||
import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton';
|
import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton';
|
||||||
import { useWorkshopsQuery } from '@/hooks/nostrQueries/useWorkshopsQuery';
|
import { useWorkshopsQuery } from '@/hooks/nostrQueries/content/useWorkshopsQuery';
|
||||||
|
|
||||||
const responsiveOptions = [
|
const responsiveOptions = [
|
||||||
{
|
{
|
||||||
@ -24,15 +24,14 @@ const responsiveOptions = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export default function WorkshopsCarousel() {
|
export default function WorkshopsCarousel() {
|
||||||
const [processedWorkshops, setProcessedWorkshops] = useState([])
|
const [processedWorkshops, setProcessedWorkshops] = useState([]);
|
||||||
const { workshops, workshopsLoading, workshopsError, refetchWorkshops } = useWorkshopsQuery()
|
const { workshops, workshopsLoading, workshopsError, refetchWorkshops } = useWorkshopsQuery();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetch = async () => {
|
const fetch = async () => {
|
||||||
try {
|
try {
|
||||||
if (workshops && workshops.length > 0) {
|
if (workshops && workshops.length > 0) {
|
||||||
const processedWorkshops = workshops.map(workshop => parseEvent(workshop));
|
const processedWorkshops = workshops.map(workshop => parseEvent(workshop));
|
||||||
|
|
||||||
setProcessedWorkshops(processedWorkshops);
|
setProcessedWorkshops(processedWorkshops);
|
||||||
} else {
|
} else {
|
||||||
console.log('No workshops fetched or empty array returned');
|
console.log('No workshops fetched or empty array returned');
|
||||||
@ -44,20 +43,21 @@ export default function WorkshopsCarousel() {
|
|||||||
fetch();
|
fetch();
|
||||||
}, [workshops]);
|
}, [workshops]);
|
||||||
|
|
||||||
if (workshopsLoading) return <div>Loading...</div>;
|
|
||||||
if (workshopsError) return <div>Error: {workshopsError}</div>;
|
if (workshopsError) return <div>Error: {workshopsError}</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h2 className="ml-[6%] mt-4">Workshops</h2>
|
<h2 className="ml-[6%] mt-4">Workshops</h2>
|
||||||
<Carousel value={!processedWorkshops.length > 0 ? [{}, {}, {}] : [...processedWorkshops]}
|
<Carousel
|
||||||
|
value={workshopsLoading || !processedWorkshops.length ? [{}, {}, {}] : [...processedWorkshops]}
|
||||||
numVisible={2}
|
numVisible={2}
|
||||||
itemTemplate={(item) =>
|
itemTemplate={(item) =>
|
||||||
processedWorkshops.length > 0 ?
|
!processedWorkshops.length ?
|
||||||
<WorkshopTemplate key={item.id} workshop={item} /> :
|
<TemplateSkeleton key={Math.random()} /> :
|
||||||
<TemplateSkeleton key={Math.random()} />
|
<WorkshopTemplate key={item.id} workshop={item} />
|
||||||
}
|
}
|
||||||
responsiveOptions={responsiveOptions} />
|
responsiveOptions={responsiveOptions}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import { formatTimestampToHowLongAgo } from "@/utils/time";
|
|||||||
import { useImageProxy } from "@/hooks/useImageProxy";
|
import { useImageProxy } from "@/hooks/useImageProxy";
|
||||||
import { getSatAmountFromInvoice } from "@/utils/lightning";
|
import { getSatAmountFromInvoice } from "@/utils/lightning";
|
||||||
import ZapDisplay from "@/components/zaps/ZapDisplay";
|
import ZapDisplay from "@/components/zaps/ZapDisplay";
|
||||||
import { useCoursesZapsQuery } from "@/hooks/nostrQueries/useCoursesZapsQuery";
|
import { useCoursesZapsQuery } from "@/hooks/nostrQueries/zaps/useCoursesZapsQuery";
|
||||||
|
|
||||||
const CourseTemplate = ({ course }) => {
|
const CourseTemplate = ({ course }) => {
|
||||||
const [zapAmount, setZapAmount] = useState(0);
|
const [zapAmount, setZapAmount] = useState(0);
|
||||||
@ -31,7 +31,6 @@ const CourseTemplate = ({ course }) => {
|
|||||||
setZapAmount(total);
|
setZapAmount(total);
|
||||||
}, [course, zaps, zapsLoading, zapsError]);
|
}, [course, zaps, zapsLoading, zapsError]);
|
||||||
|
|
||||||
if (zapsLoading) return <div>Loading...</div>;
|
|
||||||
if (zapsError) return <div>Error: {zapsError}</div>;
|
if (zapsError) return <div>Error: {zapsError}</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -65,7 +64,7 @@ const CourseTemplate = ({ course }) => {
|
|||||||
formatTimestampToHowLongAgo(course.created_at)
|
formatTimestampToHowLongAgo(course.created_at)
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<ZapDisplay zapAmount={zapAmount} event={course} />
|
<ZapDisplay zapAmount={zapAmount} event={course} zapsLoading={zapsLoading} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,7 @@ import Image from "next/image";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { formatTimestampToHowLongAgo } from "@/utils/time";
|
import { formatTimestampToHowLongAgo } from "@/utils/time";
|
||||||
import { useImageProxy } from "@/hooks/useImageProxy";
|
import { useImageProxy } from "@/hooks/useImageProxy";
|
||||||
import { useResourceZapsQuery } from "@/hooks/nostrQueries/useResourceZapsQuery";
|
import { useResourceZapsQuery } from "@/hooks/nostrQueries/zaps/useResourceZapsQuery";
|
||||||
import { getSatAmountFromInvoice } from "@/utils/lightning";
|
import { getSatAmountFromInvoice } from "@/utils/lightning";
|
||||||
import ZapDisplay from "@/components/zaps/ZapDisplay";
|
import ZapDisplay from "@/components/zaps/ZapDisplay";
|
||||||
|
|
||||||
@ -32,7 +32,6 @@ const ResourceTemplate = ({ resource }) => {
|
|||||||
setZapAmount(total);
|
setZapAmount(total);
|
||||||
}, [resource, zaps, zapsLoading, zapsError]);
|
}, [resource, zaps, zapsLoading, zapsError]);
|
||||||
|
|
||||||
if (zapsLoading) return <div>Loading...</div>;
|
|
||||||
if (zapsError) return <div>Error: {zapsError}</div>;
|
if (zapsError) return <div>Error: {zapsError}</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -63,7 +62,7 @@ const ResourceTemplate = ({ resource }) => {
|
|||||||
<p className="text-xs text-gray-400">
|
<p className="text-xs text-gray-400">
|
||||||
{formatTimestampToHowLongAgo(resource.published_at)}
|
{formatTimestampToHowLongAgo(resource.published_at)}
|
||||||
</p>
|
</p>
|
||||||
<ZapDisplay zapAmount={zapAmount} event={resource} />
|
<ZapDisplay zapAmount={zapAmount} event={resource} zapsLoading={zapsLoading} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,7 @@ import Image from "next/image";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { formatTimestampToHowLongAgo } from "@/utils/time";
|
import { formatTimestampToHowLongAgo } from "@/utils/time";
|
||||||
import { useImageProxy } from "@/hooks/useImageProxy";
|
import { useImageProxy } from "@/hooks/useImageProxy";
|
||||||
import { useWorkshopsZapsQuery } from "@/hooks/nostrQueries/useWorkshopsZapsQuery";
|
import { useWorkshopsZapsQuery } from "@/hooks/nostrQueries/zaps/useWorkshopsZapsQuery";
|
||||||
import { getSatAmountFromInvoice } from "@/utils/lightning";
|
import { getSatAmountFromInvoice } from "@/utils/lightning";
|
||||||
import ZapDisplay from "@/components/zaps/ZapDisplay";
|
import ZapDisplay from "@/components/zaps/ZapDisplay";
|
||||||
|
|
||||||
@ -31,7 +31,6 @@ const WorkshopTemplate = ({ workshop }) => {
|
|||||||
setZapAmount(total);
|
setZapAmount(total);
|
||||||
}, [zaps, workshop, zapsLoading, zapsError]);
|
}, [zaps, workshop, zapsLoading, zapsError]);
|
||||||
|
|
||||||
if (zapsLoading) return <div>Loading...</div>;
|
|
||||||
if (zapsError) return <div>Error: {zapsError}</div>;
|
if (zapsError) return <div>Error: {zapsError}</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -59,7 +58,7 @@ const WorkshopTemplate = ({ workshop }) => {
|
|||||||
<p className="text-xs text-gray-400">
|
<p className="text-xs text-gray-400">
|
||||||
{formatTimestampToHowLongAgo(workshop.published_at)}
|
{formatTimestampToHowLongAgo(workshop.published_at)}
|
||||||
</p>
|
</p>
|
||||||
<ZapDisplay zapAmount={zapAmount} event={workshop} />
|
<ZapDisplay zapAmount={zapAmount} event={workshop} zapsLoading={zapsLoading} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,16 +3,16 @@ import { OverlayPanel } from 'primereact/overlaypanel';
|
|||||||
import ZapForm from './ZapForm';
|
import ZapForm from './ZapForm';
|
||||||
import { ProgressSpinner } from 'primereact/progressspinner';
|
import { ProgressSpinner } from 'primereact/progressspinner';
|
||||||
|
|
||||||
const ZapDisplay = ({ zapAmount, event }) => {
|
const ZapDisplay = ({ zapAmount, event, zapsLoading }) => {
|
||||||
const op = useRef(null);
|
const op = useRef(null);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span className="text-xs cursor-pointer" onClick={(e) => op.current.toggle(e)}>
|
<span className="text-xs cursor-pointer flex items-center" onClick={(e) => op.current.toggle(e)}>
|
||||||
<i className="pi pi-bolt text-yellow-300"></i>
|
<i className="pi pi-bolt text-yellow-300"></i>
|
||||||
{zapAmount || zapAmount === 0 ? (
|
{zapsLoading ? (
|
||||||
zapAmount
|
<ProgressSpinner style={{width: '20px', height: '20px'}} strokeWidth="8" animationDuration=".5s" />
|
||||||
) : (
|
) : (
|
||||||
<ProgressSpinner style={{ display: 'inline-block' }} />
|
zapAmount
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<OverlayPanel className='w-[40%] h-[40%]' ref={op}>
|
<OverlayPanel className='w-[40%] h-[40%]' ref={op}>
|
||||||
|
62
src/hooks/nostrQueries/zaps/useZapsSubscription.js
Normal file
62
src/hooks/nostrQueries/zaps/useZapsSubscription.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { useNDKContext } from '@/context/NDKContext';
|
||||||
|
|
||||||
|
export function useZapsSubscription({ event }) {
|
||||||
|
const [isClient, setIsClient] = useState(false);
|
||||||
|
const [zaps, setZaps] = useState([]);
|
||||||
|
const [zapsLoading, setZapsLoading] = useState(true);
|
||||||
|
const [zapsError, setZapsError] = useState(null);
|
||||||
|
const ndk = useNDKContext();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsClient(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isClient || !ndk || !event) return;
|
||||||
|
|
||||||
|
let subscription = null;
|
||||||
|
let isSubscribed = true;
|
||||||
|
|
||||||
|
const fetchZapsFromNDK = async () => {
|
||||||
|
try {
|
||||||
|
await ndk.connect();
|
||||||
|
const uniqueEvents = new Set();
|
||||||
|
|
||||||
|
const filters = [
|
||||||
|
{ kinds: [9735], "#e": [event.id] },
|
||||||
|
{ kinds: [9735], "#a": [`${event.kind}:${event.id}:${event.d}`] }
|
||||||
|
];
|
||||||
|
|
||||||
|
subscription = ndk.subscribe(filters);
|
||||||
|
|
||||||
|
subscription.on('event', (zap) => {
|
||||||
|
if (isSubscribed) {
|
||||||
|
uniqueEvents.add(zap);
|
||||||
|
setZaps(Array.from(uniqueEvents));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
subscription.on('eose', () => {
|
||||||
|
setZaps(Array.from(uniqueEvents));
|
||||||
|
setZapsLoading(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
setZapsError('Error fetching zaps from NDK: ' + error);
|
||||||
|
setZapsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchZapsFromNDK();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
isSubscribed = false;
|
||||||
|
if (subscription) {
|
||||||
|
subscription.stop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [isClient, ndk, event]);
|
||||||
|
|
||||||
|
return { zaps, zapsLoading, zapsError };
|
||||||
|
}
|
@ -11,6 +11,7 @@ import Image from 'next/image';
|
|||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import ZapThreadsWrapper from '@/components/ZapThreadsWrapper';
|
import ZapThreadsWrapper from '@/components/ZapThreadsWrapper';
|
||||||
import { useNDKContext } from '@/context/NDKContext';
|
import { useNDKContext } from '@/context/NDKContext';
|
||||||
|
import { useZapsSubscription } from '@/hooks/nostrQueries/zaps/useZapsSubscription';
|
||||||
import 'primeicons/primeicons.css';
|
import 'primeicons/primeicons.css';
|
||||||
const MDDisplay = dynamic(
|
const MDDisplay = dynamic(
|
||||||
() => import("@uiw/react-markdown-preview"),
|
() => import("@uiw/react-markdown-preview"),
|
||||||
@ -32,12 +33,12 @@ export default function Details() {
|
|||||||
const [author, setAuthor] = useState(null);
|
const [author, setAuthor] = useState(null);
|
||||||
const [bitcoinConnect, setBitcoinConnect] = useState(false);
|
const [bitcoinConnect, setBitcoinConnect] = useState(false);
|
||||||
const [nAddress, setNAddress] = useState(null);
|
const [nAddress, setNAddress] = useState(null);
|
||||||
const [zaps, setZaps] = useState([]);
|
|
||||||
const [zapAmount, setZapAmount] = useState(0);
|
const [zapAmount, setZapAmount] = useState(0);
|
||||||
|
|
||||||
const ndk = useNDKContext();
|
const ndk = useNDKContext();
|
||||||
const [user] = useLocalStorageWithEffect('user', {});
|
const [user] = useLocalStorageWithEffect('user', {});
|
||||||
const { returnImageProxy } = useImageProxy();
|
const { returnImageProxy } = useImageProxy();
|
||||||
|
const { zaps, zapsError } = useZapsSubscription({ event: processedEvent });
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@ -135,34 +136,6 @@ export default function Details() {
|
|||||||
setZapAmount(total);
|
setZapAmount(total);
|
||||||
}, [zaps]);
|
}, [zaps]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchZaps = async () => {
|
|
||||||
try {
|
|
||||||
const processed = parseEvent(event);
|
|
||||||
await ndk.connect();
|
|
||||||
|
|
||||||
const filters = [
|
|
||||||
{
|
|
||||||
kinds: [9735],
|
|
||||||
"#e": [processed.id]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kinds: [9734],
|
|
||||||
"#a": [`${processed.kind}:${processed.id}:${processed.d}`]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const zaps = await ndk.fetchEvents(filters);
|
|
||||||
setZaps(zaps);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching zaps:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (event && ndk) {
|
|
||||||
fetchZaps();
|
|
||||||
}
|
|
||||||
}, [ndk, event]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full px-24 pt-12 mx-auto mt-4 max-tab:px-0 max-mob:px-0 max-tab:pt-2 max-mob:pt-2'>
|
<div className='w-full px-24 pt-12 mx-auto mt-4 max-tab:px-0 max-mob:px-0 max-tab:pt-2 max-mob:pt-2'>
|
||||||
<div className='w-full flex flex-row justify-between max-tab:flex-col max-mob:flex-col'>
|
<div className='w-full flex flex-row justify-between max-tab:flex-col max-mob:flex-col'>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user