1/** 2 * Redux Action Creators 3 * Centralized actions for all state mutations 4 * PRODUCTION READY 5 */ 6 7// ===== USER ACTIONS ===== 8const userLoginStart = () => ({ 9 type: "USER_LOGIN_START", 10}); 11 12const userLoginSuccess = (userData, tier) => ({ 13 type: "USER_LOGIN_SUCCESS", 14 payload: { data: userData, tier }, 15}); 16 17const userLoginFailure = (error) => ({ 18 type: "USER_LOGIN_FAILURE", 19 payload: error, 20}); 21 22const userLogout = () => ({ 23 type: "USER_LOGOUT", 24}); 25 26const userUpdate = (userData) => ({ 27 type: "USER_UPDATE", 28 payload: userData, 29}); 30 31// ===== BOOKS ACTIONS ===== 32const fetchBooksStart = () => ({ 33 type: "FETCH_BOOKS_START", 34}); 35 36const fetchBooksSuccess = (books) => ({ 37 type: "FETCH_BOOKS_SUCCESS", 38 payload: books, 39}); 40 41const fetchBooksFailure = (error) => ({ 42 type: "FETCH_BOOKS_FAILURE", 43 payload: error, 44}); 45 46const filterBooks = (filteredBooks) => ({ 47 type: "FILTER_BOOKS", 48 payload: filteredBooks, 49}); 50 51const setCurrentBook = (book) => ({ 52 type: "SET_CURRENT_BOOK", 53 payload: book, 54}); 55 56// ===== CHAPTER ACTIONS ===== 57const fetchChapterStart = () => ({ 58 type: "FETCH_CHAPTER_START", 59}); 60 61const fetchChapterSuccess = (chapter) => ({ 62 type: "FETCH_CHAPTER_SUCCESS", 63 payload: chapter, 64}); 65 66const fetchChapterFailure = (error) => ({ 67 type: "FETCH_CHAPTER_FAILURE", 68 payload: error, 69}); 70 71const cacheChapter = (chapterId, data) => ({ 72 type: "CACHE_CHAPTER", 73 payload: { chapterId, data }, 74}); 75 76// ===== UI ACTIONS ===== 77const toggleAds = (visibility) => ({ 78 type: "TOGGLE_ADS", 79 payload: visibility, 80}); 81 82const toggleInfiniteScroll = (enabled) => ({ 83 type: "TOGGLE_INFINITE_SCROLL", 84 payload: enabled, 85}); 86 87const addToast = (id, message, type = "info", duration = 3000) => ({ 88 type: "ADD_TOAST", 89 payload: { id, message, type, duration }, 90}); 91 92const removeToast = (id) => ({ 93 type: "REMOVE_TOAST", 94 payload: id, 95}); 96 97const toggleSidebar = () => ({ 98 type: "TOGGLE_SIDEBAR", 99}); 100 101// ===== CACHE ACTIONS ===== 102const updateCacheMetadata = (metadata) => ({ 103 type: "UPDATE_CACHE_METADATA", 104 payload: metadata, 105}); 106 107const clearCache = () => ({ 108 type: "CLEAR_CACHE", 109}); 110 111// ===== ASYNC THUNK-LIKE ACTIONS (Simplified) ===== 112/** 113 * Login user with error handling and state management 114 */ 115async function loginUser(email, password) { 116 try { 117 if (!appStore) { 118 throw new Error("Redux store not initialized"); 119 } 120 121 appStore.dispatch(userLoginStart()); 122 123 const response = await fetch("/signin", { 124 method: "POST", 125 headers: { "Content-Type": "application/json" }, 126 body: JSON.stringify({ email, password }), 127 }); 128 129 if (!response.ok) { 130 throw new Error(`Login failed: ${response.statusText}`); 131 } 132 133 const data = await response.json(); 134 appStore.dispatch(userLoginSuccess(data.user, data.tier)); 135 appStore.dispatch( 136 addToast(`toast-${Date.now()}`, "Logged in successfully", "success"), 137 ); 138 139 if (typeof logger !== "undefined") { 140 logger.info("Auth", "User logged in successfully"); 141 } 142 143 return data; 144 } catch (error) { 145 appStore.dispatch(userLoginFailure(error.message)); 146 appStore.dispatch(addToast(`toast-${Date.now()}`, error.message, "error")); 147 148 if (typeof logger !== "undefined") { 149 logger.error("Auth", "Login failed", error); 150 } 151 152 throw error; 153 } 154} 155 156/** 157 * Load books with caching strategy 158 */ 159async function loadBooks(forceRefresh = false) { 160 try { 161 if (!appStore) { 162 throw new Error("Redux store not initialized"); 163 } 164 165 const state = appStore.getState(); 166 const cacheAge = Date.now() - (state.books.lastFetchTime || 0); 167 const CACHE_TTL = 5 * 60 * 1000; // 5 minutes 168 169 // Use cache if valid 170 if ( 171 !forceRefresh && 172 cacheAge < CACHE_TTL && 173 state.books.allBooks.length > 0 174 ) { 175 if (typeof logger !== "undefined") { 176 logger.debug("Books", "Using cached books"); 177 } 178 return state.books.allBooks; 179 } 180 181 appStore.dispatch(fetchBooksStart()); 182 183 const response = await fetch("/allbooks"); 184 if (!response.ok) { 185 throw new Error(`Failed to fetch books: ${response.statusText}`); 186 } 187 188 const books = await response.json(); 189 appStore.dispatch(fetchBooksSuccess(books)); 190 191 if (typeof logger !== "undefined") { 192 logger.info("Books", `Loaded ${books.length} books`); 193 } 194 195 return books; 196 } catch (error) { 197 appStore.dispatch(fetchBooksFailure(error.message)); 198 appStore.dispatch( 199 addToast(`toast-${Date.now()}`, "Failed to load books", "error"), 200 ); 201 202 if (typeof logger !== "undefined") { 203 logger.error("Books", "Failed to load books", error); 204 } 205 206 throw error; 207 } 208} 209 210/** 211 * Load chapter with caching strategy 212 */ 213async function loadChapter(slug, chapterNumber) { 214 try { 215 if (!appStore) { 216 throw new Error("Redux store not initialized"); 217 } 218 219 const state = appStore.getState(); 220 221 // Check if already cached 222 const cacheKey = `${slug}-${chapterNumber}`; 223 if (state.chapters.cache[cacheKey]) { 224 if (typeof logger !== "undefined") { 225 logger.debug("Chapter", `Using cached chapter: ${cacheKey}`); 226 } 227 appStore.dispatch(fetchChapterSuccess(state.chapters.cache[cacheKey])); 228 return state.chapters.cache[cacheKey]; 229 } 230 231 appStore.dispatch(fetchChapterStart()); 232 233 const response = await fetch( 234 `/getsinglechapter?slug=${slug}&chapter=${chapterNumber}`, 235 ); 236 if (!response.ok) { 237 throw new Error(`Failed to fetch chapter: ${response.statusText}`); 238 } 239 240 const chapter = await response.json(); 241 appStore.dispatch(cacheChapter(cacheKey, chapter)); 242 appStore.dispatch(fetchChapterSuccess(chapter)); 243 244 if (typeof logger !== "undefined") { 245 logger.info( 246 "Chapter", 247 `Loaded chapter: ${slug} - Chapter ${chapterNumber}`, 248 ); 249 } 250 251 return chapter; 252 } catch (error) { 253 appStore.dispatch(fetchChapterFailure(error.message)); 254 appStore.dispatch( 255 addToast(`toast-${Date.now()}`, "Failed to load chapter", "error"), 256 ); 257 258 if (typeof logger !== "undefined") { 259 logger.error( 260 "Chapter", 261 `Failed to load chapter: ${slug}-${chapterNumber}`, 262 error, 263 ); 264 } 265 266 throw error; 267 } 268} 269 270/** 271 * Logout user 272 */ 273function logoutUser() { 274 try { 275 if (!appStore) { 276 throw new Error("Redux store not initialized"); 277 } 278 279 appStore.dispatch(userLogout()); 280 appStore.dispatch(clearCache()); 281 localStorage.removeItem("user"); 282 localStorage.removeItem("token"); 283 284 appStore.dispatch( 285 addToast(`toast-${Date.now()}`, "Logged out successfully", "success"), 286 ); 287 288 if (typeof logger !== "undefined") { 289 logger.info("Auth", "User logged out"); 290 } 291 } catch (error) { 292 if (typeof logger !== "undefined") { 293 logger.error("Auth", "Logout failed", error); 294 } 295 } 296} 297 298// Export for use in other modules 299if (typeof module !== "undefined" && module.exports) { 300 module.exports = { 301 userLoginStart, 302 userLoginSuccess, 303 userLoginFailure, 304 userLogout, 305 userUpdate, 306 fetchBooksStart, 307 fetchBooksSuccess, 308 fetchBooksFailure, 309 filterBooks, 310 setCurrentBook, 311 fetchChapterStart, 312 fetchChapterSuccess, 313 fetchChapterFailure, 314 cacheChapter, 315 toggleAds, 316 toggleInfiniteScroll, 317 addToast, 318 removeToast, 319 toggleSidebar, 320 updateCacheMetadata, 321 clearCache, 322 loginUser, 323 loadBooks, 324 loadChapter, 325 logoutUser, 326 }; 327}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.