diff --git a/.agent/logs/refactor-journal.md b/.agent/logs/refactor-journal.md index 5de6053..030a65c 100644 --- a/.agent/logs/refactor-journal.md +++ b/.agent/logs/refactor-journal.md @@ -15,3 +15,12 @@ - `src/utils/pyodide/runner.ts`: Split testing logic into dedicated private helper functions. - **๐Ÿงน Architectural Gain:** SRP โ€” `runPythonCode` now orchestrates execution and simply delegates validation to focused helper functions, increasing readability and testability. - **๐Ÿ”ฌ Verification:** Confirmed that `npx tsc --noEmit` and `npm test` passed successfully. +## 2024-05-30 โ€” Extracted Gamification Checks Logic to Pure Function + +- **๐Ÿ’ก What:** Extracted `runGamificationChecks` logic from `src/hooks/gamification/useGamificationStore.ts` into a new pure function `runPostLoadGamificationChecks` in `src/utils/gamification/loadLogic.ts`. +- **๐ŸŽฏ Why:** Code smell identified: The hook `useGamificationStore` contained inline business logic for evaluating achievements and missions on state changes, violating SRP. This makes it harder to test independently from React lifecycle. +- **๐Ÿ“ Files Changed:** + - `src/utils/gamification/loadLogic.ts`: Created `runPostLoadGamificationChecks` pure function. + - `src/hooks/gamification/useGamificationStore.ts`: Replaced inline logic with the pure function call and handled side-effects cleanly. +- **๐Ÿงน Architectural Gain:** SRP โ€” `useGamificationStore` now focuses solely on state management and side effects, while domain rules for post-load logic reside in purely testable utility functions. +- **๐Ÿ”ฌ Verification:** Confirmed that `tsc -b --noEmit`, `npm test`, `npm run lint`, and `npm run build` all pass successfully. diff --git a/public/sitemap.xml b/public/sitemap.xml index 7362e61..f2c7ffd 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -2,97 +2,97 @@ https://pyexplorer.com.br - 2026-10-09 + 2026-11-03 weekly 1.0 https://pyexplorer.com.br/login - 2026-10-09 + 2026-11-03 monthly 0.5 https://pyexplorer.com.br/register - 2026-10-09 + 2026-11-03 monthly 0.5 https://pyexplorer.com.br/learn - 2026-10-09 + 2026-11-03 weekly 0.9 https://pyexplorer.com.br/python-para-criancas - 2026-10-09 + 2026-11-03 monthly 0.5 https://pyexplorer.com.br/aprender-python-jogando - 2026-10-09 + 2026-11-03 monthly 0.5 https://pyexplorer.com.br/about - 2026-10-09 + 2026-11-03 monthly 0.5 https://pyexplorer.com.br/learn/o-que-e-python - 2026-10-09 + 2026-11-03 monthly 0.8 https://pyexplorer.com.br/learn/por-que-aprender-python - 2026-10-09 + 2026-11-03 monthly 0.8 https://pyexplorer.com.br/learn/python-para-criancas - 2026-10-09 + 2026-11-03 monthly 0.8 https://pyexplorer.com.br/learn/como-ensinar-python-criancas - 2026-10-09 + 2026-11-03 monthly 0.8 https://pyexplorer.com.br/learn/primeiros-passos-python - 2026-10-09 + 2026-11-03 monthly 0.8 https://pyexplorer.com.br/learn/jogos-aprender-programacao - 2026-10-09 + 2026-11-03 monthly 0.8 https://pyexplorer.com.br/learn/exercicios-python-criancas - 2026-10-09 + 2026-11-03 monthly 0.8 https://pyexplorer.com.br/learn/scratch-vs-python - 2026-10-09 + 2026-11-03 monthly 0.8 https://pyexplorer.com.br/learn/projetos-python-criancas - 2026-10-09 + 2026-11-03 monthly 0.8 diff --git a/src/hooks/gamification/useGamificationStore.ts b/src/hooks/gamification/useGamificationStore.ts index 7e6cc4c..282154c 100644 --- a/src/hooks/gamification/useGamificationStore.ts +++ b/src/hooks/gamification/useGamificationStore.ts @@ -2,9 +2,8 @@ import { useState, useEffect, useCallback, useRef } from 'react'; import type { UserGamification, Achievement } from '../../types/gamification'; import type { UserData } from '../../types/question'; import { - checkAndUnlockAchievements, - ensureEndgameMissions, processLoadedGamificationData, + runPostLoadGamificationChecks, } from '../../utils/gamificationState'; import { getGamification, saveGamificationWithFallback } from '../../firebase/firestore'; @@ -60,23 +59,10 @@ export function useGamificationStore({ }, [userData, isGuest, gamification.level.totalXP]); const runGamificationChecks = useCallback((currentState: UserGamification, currentBalance: number) => { - let finalState = currentState; - let hasChanges = false; - - const { newState: stateWithAchievements, unlocked } = checkAndUnlockAchievements(finalState, currentBalance); - if (unlocked.length > 0) { - finalState = stateWithAchievements; - hasChanges = true; - unlocked.forEach(a => safeAddAchievement(a)); - } - - const { newState: stateWithMissions, changed: missionsChanged } = ensureEndgameMissions(finalState); - if (missionsChanged) { - finalState = stateWithMissions; - hasChanges = true; - } + const { finalState, hasChanges, unlockedAchievements } = runPostLoadGamificationChecks(currentState, currentBalance); if (hasChanges) { + unlockedAchievements.forEach(a => safeAddAchievement(a)); setGamification(finalState); saveGamification(finalState); if (finalState.level.totalXP !== currentState.level.totalXP) { diff --git a/src/utils/gamification/loadLogic.ts b/src/utils/gamification/loadLogic.ts index 540827a..0050811 100644 --- a/src/utils/gamification/loadLogic.ts +++ b/src/utils/gamification/loadLogic.ts @@ -1,8 +1,11 @@ import type { UserGamification } from '../../types/gamification'; import { checkDailyAndWeeklyReset } from './missionLogic'; import { migrateLegacyStreak, applyStreakUpdate } from './streakLogic'; +import { checkAndUnlockAchievements } from './achievementLogic'; +import { ensureEndgameMissions } from './missionLogic'; import { calculateStreak } from '../gamificationUtils'; import { checkPetStatus, getInitialPet } from '../petLogic'; +import type { Achievement } from '../../types/gamification'; type LegacyStreakUserFields = { streak?: number; @@ -74,3 +77,27 @@ export function processLoadedGamificationData( shieldUsed: streakShieldUsed, }; } + +export function runPostLoadGamificationChecks( + currentState: UserGamification, + currentBalance: number +): { finalState: UserGamification; hasChanges: boolean; unlockedAchievements: Achievement[] } { + let finalState = currentState; + let hasChanges = false; + let unlockedAchievements: Achievement[] = []; + + const { newState: stateWithAchievements, unlocked } = checkAndUnlockAchievements(finalState, currentBalance); + if (unlocked.length > 0) { + finalState = stateWithAchievements; + hasChanges = true; + unlockedAchievements = unlocked; + } + + const { newState: stateWithMissions, changed: missionsChanged } = ensureEndgameMissions(finalState); + if (missionsChanged) { + finalState = stateWithMissions; + hasChanges = true; + } + + return { finalState, hasChanges, unlockedAchievements }; +}