/** * Badge management and achievement utilities */ import { Badge, UserBadge, RequirementType } from './types'; import { getAllBadges, getUserBadges, createUserBadge, hasUserEarnedBadge, getUserCompletedTasks, findBadge } from './db/store'; import { getBalance } from './points'; /** * Check user's progress towards badge requirements */ function checkRequirement( userId: string, requirementType: RequirementType, requirementValue: number ): boolean { switch (requirementType) { case RequirementType.POINTS_TOTAL: { const balance = getBalance(userId); return balance >= requirementValue; } case RequirementType.TASKS_COMPLETED: { const completedTasks = getUserCompletedTasks(userId); return completedTasks.length >= requirementValue; } case RequirementType.STREAK_DAYS: { // For MVP, we'll implement basic streak tracking // In production, this would check consecutive daily check-ins const completedTasks = getUserCompletedTasks(userId); const uniqueDays = new Set( completedTasks.map(task => task.completedAt.toISOString().split('T')[0] ) ); return uniqueDays.size >= requirementValue; } case RequirementType.REFERRALS: { // This would check referral count from referrals table // For MVP, simplified implementation return false; // Will be implemented with referral system } default: return false; } } /** * Check and award all eligible badges to a user */ export function checkAndAwardBadges(userId: string): Badge[] { const allBadges = getAllBadges(); const newlyEarnedBadges: Badge[] = []; for (const badge of allBadges) { // Skip if user already has this badge if (hasUserEarnedBadge(userId, badge.id)) { continue; } // Check if user meets the requirement const meetsRequirement = checkRequirement( userId, badge.requirementType, badge.requirementValue ); if (meetsRequirement) { // Award the badge createUserBadge(userId, badge.id); newlyEarnedBadges.push(badge); } } return newlyEarnedBadges; } /** * Get all badges earned by a user with badge details */ export function getUserBadgesWithDetails(userId: string): (UserBadge & { badge: Badge })[] { const userBadges = getUserBadges(userId); return userBadges.map(ub => { const badge = findBadge(ub.badgeId); if (!badge) { throw new Error(`Badge not found: ${ub.badgeId}`); } return { ...ub, badge }; }).sort((a, b) => b.earnedAt.getTime() - a.earnedAt.getTime()); } /** * Get available badges (not yet earned) */ export function getAvailableBadges(userId: string): Badge[] { const allBadges = getAllBadges(); const earnedBadgeIds = new Set( getUserBadges(userId).map(ub => ub.badgeId) ); return allBadges.filter(badge => !earnedBadgeIds.has(badge.id)); } /** * Get badge progress for a specific badge */ export function getBadgeProgress( userId: string, badgeId: string ): { badge: Badge; earned: boolean; progress: number; requirement: number; percentage: number; } { const badge = findBadge(badgeId); if (!badge) { throw new Error(`Badge not found: ${badgeId}`); } const earned = hasUserEarnedBadge(userId, badgeId); let progress = 0; const requirement = badge.requirementValue; switch (badge.requirementType) { case RequirementType.POINTS_TOTAL: progress = getBalance(userId); break; case RequirementType.TASKS_COMPLETED: progress = getUserCompletedTasks(userId).length; break; case RequirementType.STREAK_DAYS: { const completedTasks = getUserCompletedTasks(userId); const uniqueDays = new Set( completedTasks.map(task => task.completedAt.toISOString().split('T')[0] ) ); progress = uniqueDays.size; break; } case RequirementType.REFERRALS: progress = 0; // Will be implemented with referral system break; } const percentage = Math.min((progress / requirement) * 100, 100); return { badge, earned, progress, requirement, percentage }; } /** * Get all badge progress for a user */ export function getAllBadgeProgress(userId: string): ReturnType[] { const allBadges = getAllBadges(); return allBadges.map(badge => getBadgeProgress(userId, badge.id)); } /** * Get recently earned badges */ export function getRecentlyEarnedBadges( userId: string, limit: number = 5 ): (UserBadge & { badge: Badge })[] { const earnedBadges = getUserBadgesWithDetails(userId); return earnedBadges.slice(0, limit); } /** * Get badge statistics for a user */ export function getBadgeStats(userId: string): { totalEarned: number; totalAvailable: number; completionPercentage: number; recentlyEarned: (UserBadge & { badge: Badge })[]; } { const allBadges = getAllBadges(); const earnedBadges = getUserBadges(userId); const recentlyEarned = getRecentlyEarnedBadges(userId, 3); return { totalEarned: earnedBadges.length, totalAvailable: allBadges.length, completionPercentage: (earnedBadges.length / allBadges.length) * 100, recentlyEarned }; } /** * Check if badge should be awarded after a specific action */ export function checkBadgesAfterAction( userId: string, action: 'task_complete' | 'points_earned' | 'referral' | 'streak' ): Badge[] { // This is a convenience wrapper for checkAndAwardBadges // In a real implementation, you might want to optimize by only checking // relevant badges based on the action type return checkAndAwardBadges(userId); }