extract course logic into reusable hooks

This commit is contained in:
austinkelsay 2025-05-11 12:46:25 -05:00
parent f526913f30
commit 045418397c
No known key found for this signature in database
GPG Key ID: 5A763922E5BA08EE
4 changed files with 219 additions and 1 deletions

View File

@ -11,7 +11,8 @@ const appConfig = {
],
authorPubkeys: [
'f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741',
'c67cd3e1a83daa56cff16f635db2fdb9ed9619300298d4701a58e68e84098345'
'c67cd3e1a83daa56cff16f635db2fdb9ed9619300298d4701a58e68e84098345',
'6260f29fa75c91aaa292f082e5e87b438d2ab4fdf96af398567b01802ee2fcd4'
],
customLightningAddresses: [
{

View File

@ -0,0 +1,9 @@
import useCourseDecryption from '../encryption/useCourseDecryption';
import useCourseTabs from './useCourseTabs';
import useCoursePayment from './useCoursePayment';
export {
useCourseDecryption,
useCourseTabs,
useCoursePayment
};

View File

@ -0,0 +1,67 @@
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 && response?.preimage) {
// Update session to reflect purchase
const updated = await update();
showToast('success', 'Payment Success', 'You have successfully purchased this course');
return true;
} 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;

View File

@ -0,0 +1,141 @@
import { useState, useEffect, useMemo, useCallback } from 'react';
import { useRouter } from 'next/router';
import useWindowWidth from '../useWindowWidth';
/**
* 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 = windowWidth <= 968;
// Tab management state
const [activeTab, setActiveTab] = useState('overview');
const [sidebarVisible, setSidebarVisible] = useState(
options.initialSidebarVisible !== undefined ? options.initialSidebarVisible : !isMobileView
);
// Get tab map based on view mode
const tabMap = useMemo(() => {
const baseTabMap = options.tabMap || ['overview', 'content', 'qa'];
if (isMobileView) {
const mobileTabMap = [...baseTabMap];
// Insert lessons tab before qa in mobile view
if (!mobileTabMap.includes('lessons')) {
mobileTabMap.splice(2, 0, 'lessons');
}
return mobileTabMap;
}
return baseTabMap;
}, [isMobileView, options.tabMap]);
// Update tabs and sidebar based on router query
useEffect(() => {
if (router.isReady) {
const { active } = router.query;
if (active !== undefined) {
// If we have an active lesson, switch to content tab
setActiveTab('content');
} else {
// Default to overview tab when no active parameter
setActiveTab('overview');
}
// Auto-open sidebar on desktop, close on mobile
setSidebarVisible(!isMobileView);
}
}, [router.isReady, router.query, isMobileView]);
// Get active tab index
const getActiveTabIndex = useCallback(() => {
return tabMap.indexOf(activeTab);
}, [activeTab, tabMap]);
// Toggle between tabs
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');
}
}, [tabMap, isMobileView]);
// Toggle sidebar visibility
const toggleSidebar = useCallback(() => {
setSidebarVisible(prev => !prev);
}, []);
// Generate tab items for MenuTab component
const getTabItems = useCallback(() => {
const items = [
{
label: 'Overview',
icon: 'pi pi-home',
},
{
label: 'Content',
icon: 'pi pi-book',
}
];
// Add lessons tab only on mobile
if (isMobileView) {
items.push({
label: 'Lessons',
icon: 'pi pi-list',
});
}
items.push({
label: 'Comments',
icon: 'pi pi-comments',
});
return items;
}, [isMobileView]);
// Setup keyboard navigation for tabs
useEffect(() => {
const handleKeyDown = (e) => {
if (e.key === 'ArrowRight') {
const currentIndex = getActiveTabIndex();
const nextIndex = (currentIndex + 1) % tabMap.length;
toggleTab(nextIndex);
} else if (e.key === 'ArrowLeft') {
const currentIndex = getActiveTabIndex();
const prevIndex = (currentIndex - 1 + tabMap.length) % tabMap.length;
toggleTab(prevIndex);
}
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [getActiveTabIndex, tabMap, toggleTab]);
return {
activeTab,
setActiveTab,
sidebarVisible,
setSidebarVisible,
isMobileView,
toggleTab,
toggleSidebar,
getActiveTabIndex,
getTabItems,
tabMap
};
};
export default useCourseTabs;