mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-23 16:05:24 +00:00
Remove unused course and content hooks
This commit is contained in:
parent
892ac6c7be
commit
d31e28f49b
@ -1,206 +0,0 @@
|
|||||||
import React, { useEffect, useState, useRef } from 'react';
|
|
||||||
import { Tag } from 'primereact/tag';
|
|
||||||
import Image from 'next/image';
|
|
||||||
import { useImageProxy } from '@/hooks/useImageProxy';
|
|
||||||
import { getTotalFromZaps } from '@/utils/lightning';
|
|
||||||
import ZapDisplay from '@/components/zaps/ZapDisplay';
|
|
||||||
import { useZapsQuery } from '@/hooks/nostrQueries/zaps/useZapsQuery';
|
|
||||||
import { Toast } from 'primereact/toast';
|
|
||||||
import useTrackDocumentLesson from '@/hooks/tracking/useTrackDocumentLesson';
|
|
||||||
import useWindowWidth from '@/hooks/useWindowWidth';
|
|
||||||
import { nip19 } from 'nostr-tools';
|
|
||||||
import appConfig from '@/config/appConfig';
|
|
||||||
import MoreOptionsMenu from '@/components/ui/MoreOptionsMenu';
|
|
||||||
import { useSession } from 'next-auth/react';
|
|
||||||
import MarkdownDisplay from '@/components/markdown/MarkdownDisplay';
|
|
||||||
|
|
||||||
const CourseLesson = ({ lesson, course, decryptionPerformed, isPaid, setCompleted }) => {
|
|
||||||
const [zapAmount, setZapAmount] = useState(0);
|
|
||||||
const { zaps, zapsLoading, zapsError } = useZapsQuery({ event: lesson, type: 'lesson' });
|
|
||||||
const { returnImageProxy } = useImageProxy();
|
|
||||||
const menuRef = useRef(null);
|
|
||||||
const toastRef = useRef(null);
|
|
||||||
const windowWidth = useWindowWidth();
|
|
||||||
const isMobileView = windowWidth <= 768;
|
|
||||||
const { data: session } = useSession();
|
|
||||||
|
|
||||||
const readTime = lesson?.content ? Math.max(30, Math.ceil(lesson.content.length / 20)) : 60;
|
|
||||||
|
|
||||||
const { isCompleted, isTracking, markLessonAsCompleted } = useTrackDocumentLesson({
|
|
||||||
lessonId: lesson?.d,
|
|
||||||
courseId: course?.d,
|
|
||||||
readTime,
|
|
||||||
paidCourse: isPaid,
|
|
||||||
decryptionPerformed,
|
|
||||||
});
|
|
||||||
|
|
||||||
const buildMenuItems = () => {
|
|
||||||
const items = [];
|
|
||||||
|
|
||||||
const hasAccess =
|
|
||||||
session?.user && (!isPaid || decryptionPerformed || session.user.role?.subscribed);
|
|
||||||
|
|
||||||
if (hasAccess) {
|
|
||||||
items.push({
|
|
||||||
label: 'Mark as completed',
|
|
||||||
icon: 'pi pi-check-circle',
|
|
||||||
command: async () => {
|
|
||||||
try {
|
|
||||||
await markLessonAsCompleted();
|
|
||||||
setCompleted && setCompleted(lesson.id);
|
|
||||||
toastRef.current.show({
|
|
||||||
severity: 'success',
|
|
||||||
summary: 'Success',
|
|
||||||
detail: 'Lesson marked as completed',
|
|
||||||
life: 3000,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to mark lesson as completed:', error);
|
|
||||||
toastRef.current.show({
|
|
||||||
severity: 'error',
|
|
||||||
summary: 'Error',
|
|
||||||
detail: 'Failed to mark lesson as completed',
|
|
||||||
life: 3000,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
items.push({
|
|
||||||
label: 'Open lesson',
|
|
||||||
icon: 'pi pi-arrow-up-right',
|
|
||||||
command: () => {
|
|
||||||
window.open(`/details/${lesson.id}`, '_blank');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
items.push({
|
|
||||||
label: 'View Nostr note',
|
|
||||||
icon: 'pi pi-globe',
|
|
||||||
command: () => {
|
|
||||||
if (lesson?.d) {
|
|
||||||
const addr = nip19.naddrEncode({
|
|
||||||
pubkey: lesson.pubkey,
|
|
||||||
kind: lesson.kind,
|
|
||||||
identifier: lesson.d,
|
|
||||||
relays: appConfig.defaultRelayUrls || [],
|
|
||||||
});
|
|
||||||
window.open(`https://habla.news/a/${addr}`, '_blank');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return items;
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!zaps || zapsLoading || zapsError) return;
|
|
||||||
|
|
||||||
const total = getTotalFromZaps(zaps, lesson);
|
|
||||||
|
|
||||||
setZapAmount(total);
|
|
||||||
}, [zaps, zapsLoading, zapsError, lesson]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isCompleted && !isTracking && setCompleted) {
|
|
||||||
setCompleted(lesson.id);
|
|
||||||
}
|
|
||||||
}, [isCompleted, isTracking, lesson.id, setCompleted]);
|
|
||||||
|
|
||||||
const renderContent = () => {
|
|
||||||
if (isPaid && decryptionPerformed) {
|
|
||||||
return <MarkdownDisplay content={lesson.content} className="p-4 rounded-lg w-full" />;
|
|
||||||
}
|
|
||||||
if (isPaid && !decryptionPerformed) {
|
|
||||||
return (
|
|
||||||
<p className="text-center text-xl text-red-500">
|
|
||||||
This content is paid and needs to be purchased before viewing.
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (lesson?.content) {
|
|
||||||
return <MarkdownDisplay content={lesson.content} className="p-4 rounded-lg w-full" />;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
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">
|
|
||||||
<Toast ref={toastRef} />
|
|
||||||
<div className="w-full flex flex-row justify-between max-tab:flex-col max-mob:flex-col">
|
|
||||||
<div className="w-[75vw] mx-auto flex flex-row items-start justify-between max-tab:flex-col max-mob:flex-col max-tab:w-[95vw] max-mob:w-[95vw]">
|
|
||||||
<div className="flex flex-col items-start max-w-[45vw] max-tab:max-w-[100vw] max-mob:max-w-[100vw]">
|
|
||||||
<div className="flex flex-row items-center justify-between w-full">
|
|
||||||
<h1 className="text-4xl">{lesson?.title}</h1>
|
|
||||||
<ZapDisplay zapAmount={zapAmount} event={lesson} zapsLoading={zapsLoading} />
|
|
||||||
</div>
|
|
||||||
<div className="pt-2 flex flex-row justify-start w-full mt-2 mb-4">
|
|
||||||
{lesson &&
|
|
||||||
lesson.topics &&
|
|
||||||
lesson.topics.length > 0 &&
|
|
||||||
lesson.topics.map((topic, index) => (
|
|
||||||
<Tag className="mr-2 text-white" key={index} value={topic}></Tag>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="text-xl mt-6">
|
|
||||||
{lesson?.summary && (
|
|
||||||
<div className="text-xl mt-4">
|
|
||||||
{lesson.summary.split('\n').map((line, index) => (
|
|
||||||
<p key={index}>{line}</p>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between w-full mt-8">
|
|
||||||
<div className="flex flex-row w-fit items-center">
|
|
||||||
<Image
|
|
||||||
alt="avatar thumbnail"
|
|
||||||
src={returnImageProxy(lesson.author?.avatar, lesson.author?.pubkey)}
|
|
||||||
width={50}
|
|
||||||
height={50}
|
|
||||||
className="rounded-full mr-4"
|
|
||||||
/>
|
|
||||||
<p className="text-lg">
|
|
||||||
Created by{' '}
|
|
||||||
<a
|
|
||||||
rel="noreferrer noopener"
|
|
||||||
target="_blank"
|
|
||||||
className="text-blue-500 hover:underline"
|
|
||||||
>
|
|
||||||
{lesson.author?.username || lesson.author?.pubkey}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<MoreOptionsMenu
|
|
||||||
menuItems={buildMenuItems()}
|
|
||||||
additionalLinks={lesson?.additionalLinks || []}
|
|
||||||
isMobileView={isMobileView}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col max-tab:mt-12 max-mob:mt-12">
|
|
||||||
{lesson && (
|
|
||||||
<div className="flex flex-col items-center justify-between rounded-lg h-72 p-4 bg-gray-700 drop-shadow-md">
|
|
||||||
<Image
|
|
||||||
alt="course thumbnail"
|
|
||||||
src={returnImageProxy(lesson.image)}
|
|
||||||
width={344}
|
|
||||||
height={194}
|
|
||||||
className="w-[344px] h-[194px] object-cover object-top rounded-lg"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="w-[75vw] mx-auto mt-12 p-12 border-t-2 border-gray-300 max-tab:p-0 max-mob:p-0 max-tab:max-w-[100vw] max-mob:max-w-[100vw]">
|
|
||||||
{renderContent()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CourseLesson;
|
|
@ -1,30 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { cva } from 'class-variance-authority';
|
|
||||||
|
|
||||||
import { cn } from '@/utils/tw';
|
|
||||||
|
|
||||||
const badgeVariants = cva(
|
|
||||||
'inline-flex items-center rounded-full border border-neutral-200 px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-neutral-950 focus:ring-offset-2 dark:border-neutral-800 dark:focus:ring-neutral-300',
|
|
||||||
{
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
default:
|
|
||||||
'border-transparent bg-neutral-900 text-neutral-50 hover:bg-neutral-900/80 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50/80',
|
|
||||||
secondary:
|
|
||||||
'border-transparent bg-neutral-100 text-neutral-900 hover:bg-neutral-100/80 dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/80',
|
|
||||||
destructive:
|
|
||||||
'border-transparent bg-red-500 text-neutral-50 hover:bg-red-500/80 dark:bg-red-900 dark:text-neutral-50 dark:hover:bg-red-900/80',
|
|
||||||
outline: 'text-neutral-950 dark:text-neutral-50',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
variant: 'default',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
function Badge({ className, variant, ...props }) {
|
|
||||||
return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Badge, badgeVariants };
|
|
@ -1,44 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { Slot } from '@radix-ui/react-slot';
|
|
||||||
import { cva } from 'class-variance-authority';
|
|
||||||
|
|
||||||
import { cn } from '@/utils/tw';
|
|
||||||
|
|
||||||
const buttonVariants = cva(
|
|
||||||
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-neutral-950 dark:focus-visible:ring-neutral-300',
|
|
||||||
{
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
default:
|
|
||||||
'bg-neutral-900 text-neutral-50 hover:bg-neutral-900/90 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50/90',
|
|
||||||
destructive:
|
|
||||||
'bg-red-500 text-neutral-50 hover:bg-red-500/90 dark:bg-red-900 dark:text-neutral-50 dark:hover:bg-red-900/90',
|
|
||||||
outline:
|
|
||||||
'border border-neutral-200 bg-white hover:bg-neutral-100 hover:text-neutral-900 dark:border-neutral-800 dark:bg-neutral-950 dark:hover:bg-neutral-800 dark:hover:text-neutral-50',
|
|
||||||
secondary:
|
|
||||||
'bg-neutral-100 text-neutral-900 hover:bg-neutral-100/80 dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/80',
|
|
||||||
ghost:
|
|
||||||
'hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-neutral-800 dark:hover:text-neutral-50',
|
|
||||||
link: 'text-neutral-900 underline-offset-4 hover:underline dark:text-neutral-50',
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
default: 'h-10 px-4 py-2',
|
|
||||||
sm: 'h-9 rounded-md px-3',
|
|
||||||
lg: 'h-11 rounded-md px-8',
|
|
||||||
icon: 'h-10 w-10',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
variant: 'default',
|
|
||||||
size: 'default',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
||||||
const Comp = asChild ? Slot : 'button';
|
|
||||||
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
|
|
||||||
});
|
|
||||||
Button.displayName = 'Button';
|
|
||||||
|
|
||||||
export { Button, buttonVariants };
|
|
@ -1,17 +0,0 @@
|
|||||||
import useCourseDecryption from '../encryption/useCourseDecryption';
|
|
||||||
import useCourseTabs from './useCourseTabs';
|
|
||||||
import useCoursePayment from './useCoursePayment';
|
|
||||||
import useCourseData from './useCourseData';
|
|
||||||
import useLessons from './useLessons';
|
|
||||||
import useCourseNavigation from './useCourseNavigation';
|
|
||||||
import useCourseTabsState from './useCourseTabsState';
|
|
||||||
|
|
||||||
export {
|
|
||||||
useCourseDecryption,
|
|
||||||
useCourseTabs,
|
|
||||||
useCoursePayment,
|
|
||||||
useCourseData,
|
|
||||||
useLessons,
|
|
||||||
useCourseNavigation,
|
|
||||||
useCourseTabsState
|
|
||||||
};
|
|
@ -1,79 +0,0 @@
|
|||||||
import { useCallback, useMemo } from 'react';
|
|
||||||
import { useToast } from '../useToast';
|
|
||||||
import { useSession } from 'next-auth/react';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook to handle course payment processing and authorization
|
|
||||||
* @param {Object} course - The course object
|
|
||||||
* @returns {Object} Payment handling utilities and authorization state
|
|
||||||
*/
|
|
||||||
const useCoursePayment = (course) => {
|
|
||||||
const { data: session, update } = useSession();
|
|
||||||
const { showToast } = useToast();
|
|
||||||
|
|
||||||
// Determine if course requires payment
|
|
||||||
const isPaidCourse = useMemo(() => {
|
|
||||||
return course?.price && course.price > 0;
|
|
||||||
}, [course]);
|
|
||||||
|
|
||||||
// Check if user is authorized to access the course
|
|
||||||
const isAuthorized = useMemo(() => {
|
|
||||||
if (!session?.user || !course) return !isPaidCourse; // Free courses are always authorized
|
|
||||||
|
|
||||||
return (
|
|
||||||
// User is subscribed
|
|
||||||
session.user.role?.subscribed ||
|
|
||||||
// User is the creator of the course
|
|
||||||
session.user.pubkey === course.pubkey ||
|
|
||||||
// Course is free
|
|
||||||
!isPaidCourse ||
|
|
||||||
// User has purchased this specific course
|
|
||||||
session.user.purchased?.some(purchase => purchase.courseId === course.d)
|
|
||||||
);
|
|
||||||
}, [session, course, isPaidCourse]);
|
|
||||||
|
|
||||||
// Handler for successful payment
|
|
||||||
const handlePaymentSuccess = useCallback(async (response) => {
|
|
||||||
if (response?.preimage) {
|
|
||||||
try {
|
|
||||||
await update(); // refresh session
|
|
||||||
showToast(
|
|
||||||
'success',
|
|
||||||
'Payment Success',
|
|
||||||
'You have successfully purchased this course'
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
} catch (err) {
|
|
||||||
showToast(
|
|
||||||
'warn',
|
|
||||||
'Session Refresh Failed',
|
|
||||||
'Purchase succeeded but we could not refresh your session automatically. Please reload the page.'
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
showToast('error', 'Error', 'Failed to purchase course. Please try again.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}, [update, showToast]);
|
|
||||||
|
|
||||||
// Handler for payment errors
|
|
||||||
const handlePaymentError = useCallback((error) => {
|
|
||||||
showToast(
|
|
||||||
'error',
|
|
||||||
'Payment Error',
|
|
||||||
`Failed to purchase course. Please try again. Error: ${error}`
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}, [showToast]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
isPaidCourse,
|
|
||||||
isAuthorized,
|
|
||||||
handlePaymentSuccess,
|
|
||||||
handlePaymentError,
|
|
||||||
session
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useCoursePayment;
|
|
@ -1,92 +0,0 @@
|
|||||||
import { useEffect, useCallback } from 'react';
|
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
import useWindowWidth from '../useWindowWidth';
|
|
||||||
import useCourseTabsState from './useCourseTabsState';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use useCourseTabsState for pure state or useCourseNavigation for router integration
|
|
||||||
* Hook to manage course tabs, navigation, and sidebar visibility
|
|
||||||
* @param {Object} options - Configuration options
|
|
||||||
* @param {Array} options.tabMap - Optional custom tab map to use
|
|
||||||
* @param {boolean} options.initialSidebarVisible - Initial sidebar visibility state
|
|
||||||
* @returns {Object} Tab management utilities and state
|
|
||||||
*/
|
|
||||||
const useCourseTabs = (options = {}) => {
|
|
||||||
const router = useRouter();
|
|
||||||
const windowWidth = useWindowWidth();
|
|
||||||
const isMobileView = typeof windowWidth === 'number' ? windowWidth <= 968 : false;
|
|
||||||
|
|
||||||
// Use the base hook for core tab state functionality
|
|
||||||
const {
|
|
||||||
activeTab,
|
|
||||||
setActiveTab,
|
|
||||||
sidebarVisible,
|
|
||||||
setSidebarVisible,
|
|
||||||
tabMap,
|
|
||||||
getActiveTabIndex,
|
|
||||||
getTabItems,
|
|
||||||
toggleSidebar
|
|
||||||
} = useCourseTabsState({
|
|
||||||
tabMap: options.tabMap,
|
|
||||||
initialSidebarVisible: options.initialSidebarVisible,
|
|
||||||
isMobileView
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update tabs and sidebar based on router query
|
|
||||||
useEffect(() => {
|
|
||||||
if (router.isReady) {
|
|
||||||
const { active, tab } = router.query;
|
|
||||||
|
|
||||||
// If tab is specified in the URL, use that
|
|
||||||
if (tab && tabMap.includes(tab)) {
|
|
||||||
setActiveTab(tab);
|
|
||||||
} else if (active !== undefined) {
|
|
||||||
// If we have an active lesson, switch to content tab
|
|
||||||
setActiveTab('content');
|
|
||||||
} else {
|
|
||||||
// Default to overview tab when no parameters
|
|
||||||
setActiveTab('overview');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [router.isReady, router.query, tabMap, setActiveTab]);
|
|
||||||
|
|
||||||
// Toggle between tabs with router integration
|
|
||||||
const toggleTab = useCallback((indexOrName) => {
|
|
||||||
const tabName = typeof indexOrName === 'number'
|
|
||||||
? tabMap[indexOrName]
|
|
||||||
: indexOrName;
|
|
||||||
|
|
||||||
setActiveTab(tabName);
|
|
||||||
|
|
||||||
// Only show/hide sidebar on mobile - desktop keeps sidebar visible
|
|
||||||
if (isMobileView) {
|
|
||||||
setSidebarVisible(tabName === 'lessons');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sync URL with tab change using shallow routing
|
|
||||||
const newQuery = {
|
|
||||||
...router.query,
|
|
||||||
tab: tabName === 'overview' ? undefined : tabName
|
|
||||||
};
|
|
||||||
router.push(
|
|
||||||
{ pathname: router.pathname, query: newQuery },
|
|
||||||
undefined,
|
|
||||||
{ shallow: true }
|
|
||||||
);
|
|
||||||
}, [tabMap, isMobileView, router, setActiveTab, setSidebarVisible]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
activeTab,
|
|
||||||
setActiveTab,
|
|
||||||
sidebarVisible,
|
|
||||||
setSidebarVisible,
|
|
||||||
isMobileView,
|
|
||||||
toggleTab,
|
|
||||||
toggleSidebar,
|
|
||||||
getActiveTabIndex,
|
|
||||||
getTabItems,
|
|
||||||
tabMap
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useCourseTabs;
|
|
@ -1,45 +0,0 @@
|
|||||||
import { useState, useEffect } from 'react';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { useNDKContext } from '@/context/NDKContext';
|
|
||||||
|
|
||||||
export function useAllContentQuery({ ids }) {
|
|
||||||
const [isClient, setIsClient] = useState(false);
|
|
||||||
const { ndk, addSigner } = useNDKContext();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setIsClient(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const fetchAllContentFromNDK = async ids => {
|
|
||||||
try {
|
|
||||||
await ndk.connect();
|
|
||||||
|
|
||||||
const filter = { ids: ids };
|
|
||||||
const events = await ndk.fetchEvents(filter);
|
|
||||||
|
|
||||||
if (events && events.size > 0) {
|
|
||||||
const eventsArray = Array.from(events);
|
|
||||||
return eventsArray;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching videos from NDK:', error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const {
|
|
||||||
data: allContent,
|
|
||||||
isLoading: allContentLoading,
|
|
||||||
error: allContentError,
|
|
||||||
refetch: refetchAllContent,
|
|
||||||
} = useQuery({
|
|
||||||
queryKey: ['allContent', isClient],
|
|
||||||
queryFn: () => fetchAllContentFromNDK(ids),
|
|
||||||
staleTime: 1000 * 60 * 30, // 30 minutes
|
|
||||||
refetchInterval: 1000 * 60 * 30, // 30 minutes
|
|
||||||
enabled: isClient,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { allContent, allContentLoading, allContentError, refetchAllContent };
|
|
||||||
}
|
|
@ -1,60 +0,0 @@
|
|||||||
import { useState, useEffect } from 'react';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { useNDKContext } from '@/context/NDKContext';
|
|
||||||
import axios from 'axios';
|
|
||||||
import appConfig from '@/config/appConfig';
|
|
||||||
|
|
||||||
export function useCoursesQuery() {
|
|
||||||
const [isClient, setIsClient] = useState(false);
|
|
||||||
const { ndk, addSigner } = useNDKContext();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setIsClient(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const hasRequiredProperties = (event, contentIds) => {
|
|
||||||
const hasId = event.tags.some(([tag, value]) => tag === 'd' && contentIds.includes(value));
|
|
||||||
return hasId;
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchCoursesFromNDK = async () => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`/api/content/all`);
|
|
||||||
const contentIds = response.data;
|
|
||||||
|
|
||||||
if (!contentIds || contentIds.length === 0) {
|
|
||||||
return []; // Return early if no content IDs are found
|
|
||||||
}
|
|
||||||
|
|
||||||
await ndk.connect();
|
|
||||||
|
|
||||||
const filter = { kinds: [30004], authors: appConfig.authorPubkeys };
|
|
||||||
const events = await ndk.fetchEvents(filter);
|
|
||||||
|
|
||||||
if (events && events.size > 0) {
|
|
||||||
const eventsArray = Array.from(events);
|
|
||||||
const courses = eventsArray.filter(event => hasRequiredProperties(event, contentIds));
|
|
||||||
return courses;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching courses from NDK:', error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const {
|
|
||||||
data: courses,
|
|
||||||
isLoading: coursesLoading,
|
|
||||||
error: coursesError,
|
|
||||||
refetch: refetchCourses,
|
|
||||||
} = useQuery({
|
|
||||||
queryKey: ['courses', isClient],
|
|
||||||
queryFn: fetchCoursesFromNDK,
|
|
||||||
// staleTime: 1000 * 60 * 30, // 30 minutes
|
|
||||||
// refetchInterval: 1000 * 60 * 30, // 30 minutes
|
|
||||||
enabled: isClient,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { courses, coursesLoading, coursesError, refetchCourses };
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
import { useState, useEffect } from 'react';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { useNDKContext } from '@/context/NDKContext';
|
|
||||||
import axios from 'axios';
|
|
||||||
import appConfig from '@/config/appConfig';
|
|
||||||
|
|
||||||
export function useDocumentsQuery() {
|
|
||||||
const [isClient, setIsClient] = useState(false);
|
|
||||||
const { ndk, addSigner } = useNDKContext();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setIsClient(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const hasRequiredProperties = (event, contentIds) => {
|
|
||||||
const hasDocument = event.tags.some(([tag, value]) => tag === 't' && value === 'document');
|
|
||||||
const hasId = event.tags.some(([tag, value]) => tag === 'd' && contentIds.includes(value));
|
|
||||||
return hasDocument && hasId;
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchDocumentsFromNDK = async () => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`/api/content/all`);
|
|
||||||
const contentIds = response.data;
|
|
||||||
|
|
||||||
if (!contentIds || contentIds.length === 0) {
|
|
||||||
return []; // Return early if no content IDs are found
|
|
||||||
}
|
|
||||||
|
|
||||||
await ndk.connect();
|
|
||||||
|
|
||||||
const filter = { kinds: [30023, 30402], authors: appConfig.authorPubkeys };
|
|
||||||
const events = await ndk.fetchEvents(filter);
|
|
||||||
|
|
||||||
if (events && events.size > 0) {
|
|
||||||
const eventsArray = Array.from(events);
|
|
||||||
const documents = eventsArray.filter(event => hasRequiredProperties(event, contentIds));
|
|
||||||
return documents;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching documents from NDK:', error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const {
|
|
||||||
data: documents,
|
|
||||||
isLoading: documentsLoading,
|
|
||||||
error: documentsError,
|
|
||||||
refetch: refetchDocuments,
|
|
||||||
} = useQuery({
|
|
||||||
queryKey: ['documents', isClient],
|
|
||||||
queryFn: fetchDocumentsFromNDK,
|
|
||||||
// staleTime: 1000 * 60 * 30, // 30 minutes
|
|
||||||
// refetchInterval: 1000 * 60 * 30, // 30 minutes
|
|
||||||
enabled: isClient,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { documents, documentsLoading, documentsError, refetchDocuments };
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
import { useState, useEffect } from 'react';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { useNDKContext } from '@/context/NDKContext';
|
|
||||||
import axios from 'axios';
|
|
||||||
import appConfig from '@/config/appConfig';
|
|
||||||
|
|
||||||
export function useVideosQuery() {
|
|
||||||
const [isClient, setIsClient] = useState(false);
|
|
||||||
const { ndk, addSigner } = useNDKContext();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setIsClient(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const hasRequiredProperties = (event, contentIds) => {
|
|
||||||
const hasVideo = event.tags.some(([tag, value]) => tag === 't' && value === 'video');
|
|
||||||
const hasId = event.tags.some(([tag, value]) => tag === 'd' && contentIds.includes(value));
|
|
||||||
return hasVideo && hasId;
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchVideosFromNDK = async () => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`/api/content/all`);
|
|
||||||
const contentIds = response.data;
|
|
||||||
|
|
||||||
if (!contentIds || contentIds.length === 0) {
|
|
||||||
return []; // Return early if no content IDs are found
|
|
||||||
}
|
|
||||||
|
|
||||||
await ndk.connect();
|
|
||||||
|
|
||||||
const filter = { kinds: [30023, 30402], authors: appConfig.authorPubkeys };
|
|
||||||
const events = await ndk.fetchEvents(filter);
|
|
||||||
|
|
||||||
if (events && events.size > 0) {
|
|
||||||
const eventsArray = Array.from(events);
|
|
||||||
const videos = eventsArray.filter(event => hasRequiredProperties(event, contentIds));
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching videos from NDK:', error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const {
|
|
||||||
data: videos,
|
|
||||||
isLoading: videosLoading,
|
|
||||||
error: videosError,
|
|
||||||
refetch: refetchVideos,
|
|
||||||
} = useQuery({
|
|
||||||
queryKey: ['videos', isClient],
|
|
||||||
queryFn: fetchVideosFromNDK,
|
|
||||||
// staleTime: 1000 * 60 * 30, // 30 minutes
|
|
||||||
// refetchInterval: 1000 * 60 * 30, // 30 minutes
|
|
||||||
enabled: isClient,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { videos, videosLoading, videosError, refetchVideos };
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user