PluginProbe ʕ •ᴥ•ʔ
Presto Player / 4.3.2
Presto Player v4.3.2
4.3.3 4.3.2 4.3.1 4.3.0 4.2.4 4.2.3 4.2.2 4.2.0 4.2.1 trunk 1.10.0 1.10.1 1.10.2 1.11.0 1.12.0 1.13.0 1.14.0 1.14.1 1.5.10 1.5.11 1.5.12 1.5.13 1.5.14 1.5.15 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.10 1.6.11 1.6.12 1.6.13 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.8.0 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.11 2.0.12 2.0.13 2.0.14 2.0.15 2.0.16 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.2.0 2.2.1 2.2.2 2.2.3 2.2.3-beta1 2.3.0 2.3.1 2.3.2 2.3.3 3.0.0 3.0.0-beta1 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.1.0 3.1.1 3.1.2 3.1.3 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4
presto-player / src / admin / dashboard / lib / bsf-learn / useBsfLearn.js
presto-player / src / admin / dashboard / lib / bsf-learn Last commit date
components 2 months ago helpers.js 2 months ago index.js 2 months ago useBsfLearn.js 2 months ago
useBsfLearn.js
181 lines
1 import { useState, useEffect, useMemo, useCallback } from 'react';
2 import apiFetch from '@wordpress/api-fetch';
3 import { toast } from '@bsf/force-ui';
4 import { __ } from '@wordpress/i18n';
5
6 /**
7 * Custom hook for managing Learn chapters and steps
8 *
9 * @param {Object} config - Configuration object
10 * @param {Array} config.initialChapters - Array of chapter objects with steps
11 * @param {string} config.saveEndpoint - API endpoint to save progress (optional)
12 * @returns {Object} - Object containing chapters state and utility functions
13 */
14 const useBsfLearn = ( {
15 initialChapters = [],
16 saveEndpoint = null,
17 } = {} ) => {
18 const [ chapters, setChapters ] = useState( initialChapters );
19 const [ learnHowDialogOpen, setLearnHowDialogOpen ] = useState( false );
20 const [ currentLearnHowItem, setCurrentLearnHowItem ] = useState( null );
21
22 // Update chapters when initialChapters changes (e.g., when API data loads)
23 useEffect( () => {
24 if ( initialChapters.length > 0 ) {
25 setChapters( initialChapters );
26 }
27 }, [ initialChapters ] );
28
29 /**
30 * Update completion status of a specific step
31 */
32 const updateStepCompletion = useCallback(
33 ( chapterId, stepId, completed ) => {
34 // Optimistically update UI
35 setChapters( ( prevChapters ) =>
36 prevChapters.map( ( chapter ) =>
37 chapter.id === chapterId
38 ? {
39 ...chapter,
40 steps: chapter.steps.map( ( step ) =>
41 step.id === stepId ? { ...step, completed } : step
42 ),
43 }
44 : chapter
45 )
46 );
47
48 // Save to API if endpoint is provided
49 if ( saveEndpoint ) {
50 apiFetch( {
51 path: saveEndpoint,
52 method: 'POST',
53 data: {
54 chapterId,
55 stepId,
56 completed,
57 },
58 } ).catch( ( error ) => {
59 // Revert UI state on error
60 setChapters( ( prevChapters ) =>
61 prevChapters.map( ( chapter ) =>
62 chapter.id === chapterId
63 ? {
64 ...chapter,
65 steps: chapter.steps.map( ( step ) =>
66 step.id === stepId ? { ...step, completed: ! completed } : step
67 ),
68 }
69 : chapter
70 )
71 );
72
73 toast.error( __( 'Failed to save progress. Please try again.', 'presto-player' ) );
74 console.error( 'Failed to save progress:', error );
75 } );
76 }
77 },
78 [ saveEndpoint ]
79 );
80
81 const markStepCompleted = useCallback(
82 ( chapterId, stepId ) => {
83 updateStepCompletion( chapterId, stepId, true );
84 },
85 [ updateStepCompletion ]
86 );
87
88 const markStepIncomplete = useCallback(
89 ( chapterId, stepId ) => {
90 updateStepCompletion( chapterId, stepId, false );
91 },
92 [ updateStepCompletion ]
93 );
94
95 const resetProgress = useCallback( () => {
96 setChapters( ( prevChapters ) =>
97 prevChapters.map( ( chapter ) => ( {
98 ...chapter,
99 steps: chapter.steps.map( ( step ) => ( { ...step, completed: false } ) ),
100 } ) )
101 );
102 }, [] );
103
104 const firstIncompleteChapterId = useMemo( () => {
105 const incompleteChapter = chapters.find(
106 ( chapter ) =>
107 chapter.steps.length !==
108 chapter.steps.filter( ( step ) => step.completed ).length
109 );
110 return incompleteChapter?.id;
111 }, [ chapters ] );
112
113 const progressStats = useMemo( () => {
114 const totalSteps = chapters.reduce( ( sum, chapter ) => sum + chapter.steps.length, 0 );
115 const completedSteps = chapters.reduce(
116 ( sum, chapter ) =>
117 sum + chapter.steps.filter( ( step ) => step.completed ).length,
118 0
119 );
120 const completionPercentage =
121 totalSteps > 0 ? Math.round( ( completedSteps / totalSteps ) * 100 ) : 0;
122
123 return {
124 totalChapters: chapters.length,
125 totalSteps,
126 completedSteps,
127 completionPercentage,
128 isFullyCompleted: totalSteps > 0 && completedSteps === totalSteps,
129 };
130 }, [ chapters ] );
131
132 const getChapterStats = useCallback(
133 ( chapterId ) => {
134 const chapter = chapters.find( ( c ) => c.id === chapterId );
135 if ( ! chapter ) {
136 return null;
137 }
138
139 const totalSteps = chapter.steps.length;
140 const completedSteps = chapter.steps.filter( ( step ) => step.completed ).length;
141
142 return {
143 totalSteps,
144 completedSteps,
145 isCompleted: totalSteps > 0 && completedSteps === totalSteps,
146 completionPercentage:
147 totalSteps > 0 ? Math.round( ( completedSteps / totalSteps ) * 100 ) : 0,
148 };
149 },
150 [ chapters ]
151 );
152
153 const openLearnHowDialog = useCallback( ( item ) => {
154 setCurrentLearnHowItem( item );
155 setLearnHowDialogOpen( true );
156 }, [] );
157
158 const closeLearnHowDialog = useCallback( () => {
159 setLearnHowDialogOpen( false );
160 setCurrentLearnHowItem( null );
161 }, [] );
162
163 return {
164 chapters,
165 updateStepCompletion,
166 markStepCompleted,
167 markStepIncomplete,
168 resetProgress,
169 firstIncompleteChapterId,
170 progressStats,
171 getChapterStats,
172 learnHowDialogOpen,
173 currentLearnHowItem,
174 openLearnHowDialog,
175 closeLearnHowDialog,
176 setLearnHowDialogOpen,
177 };
178 };
179
180 export default useBsfLearn;
181