mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-03 07:42:03 +00:00
extract course logic into reusable hooks
This commit is contained in:
parent
f526913f30
commit
045418397c
@ -11,7 +11,8 @@ const appConfig = {
|
||||
],
|
||||
authorPubkeys: [
|
||||
'f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741',
|
||||
'c67cd3e1a83daa56cff16f635db2fdb9ed9619300298d4701a58e68e84098345'
|
||||
'c67cd3e1a83daa56cff16f635db2fdb9ed9619300298d4701a58e68e84098345',
|
||||
'6260f29fa75c91aaa292f082e5e87b438d2ab4fdf96af398567b01802ee2fcd4'
|
||||
],
|
||||
customLightningAddresses: [
|
||||
{
|
||||
|
9
src/hooks/courses/index.js
Normal file
9
src/hooks/courses/index.js
Normal file
@ -0,0 +1,9 @@
|
||||
import useCourseDecryption from '../encryption/useCourseDecryption';
|
||||
import useCourseTabs from './useCourseTabs';
|
||||
import useCoursePayment from './useCoursePayment';
|
||||
|
||||
export {
|
||||
useCourseDecryption,
|
||||
useCourseTabs,
|
||||
useCoursePayment
|
||||
};
|
67
src/hooks/courses/useCoursePayment.js
Normal file
67
src/hooks/courses/useCoursePayment.js
Normal 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;
|
141
src/hooks/courses/useCourseTabs.js
Normal file
141
src/hooks/courses/useCourseTabs.js
Normal 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;
|
Loading…
x
Reference in New Issue
Block a user