|
|
|
const XP_PER_LEVEL = 1000 |
|
const XP_GROWTH_FACTOR = 1.2 |
|
|
|
export const calculateLevel = (xp: number): number => { |
|
return Math.floor(xp / XP_PER_LEVEL) + 1 |
|
} |
|
|
|
export const xpToNextLevel = (xp: number): number => { |
|
const currentLevel = calculateLevel(xp) |
|
return currentLevel * XP_PER_LEVEL * XP_GROWTH_FACTOR - xp |
|
} |
|
|
|
|
|
export const calculateFLBReward = ( |
|
difficulty: "easy" | "medium" | "hard" | "expert", |
|
timeTaken: number, |
|
accuracy: number, |
|
streakBonus: number, |
|
): number => { |
|
const baseRewards = { |
|
easy: 10, |
|
medium: 25, |
|
hard: 50, |
|
expert: 100, |
|
} |
|
|
|
|
|
const timeFactor = Math.max(0, 1 - timeTaken / 30) |
|
|
|
|
|
const accuracyMultiplier = accuracy / 100 |
|
|
|
|
|
const streakMultiplier = 1 + streakBonus * 0.02 |
|
|
|
return Math.round(baseRewards[difficulty] * timeFactor * accuracyMultiplier * streakMultiplier) |
|
} |
|
|
|
|
|
export const updateStreak = (lastActivity: Date): number => { |
|
const today = new Date() |
|
const lastDate = new Date(lastActivity) |
|
const diffDays = Math.floor((today.getTime() - lastDate.getTime()) / (1000 * 60 * 60 * 24)) |
|
|
|
|
|
if (diffDays === 1) { |
|
const currentStreak = Number.parseInt(localStorage.getItem("currentStreak") || "0") |
|
const newStreak = currentStreak + 1 |
|
localStorage.setItem("currentStreak", newStreak.toString()) |
|
return newStreak |
|
} |
|
|
|
|
|
if (diffDays === 0) { |
|
return Number.parseInt(localStorage.getItem("currentStreak") || "1") |
|
} |
|
|
|
|
|
localStorage.setItem("currentStreak", "1") |
|
return 1 |
|
} |
|
|
|
|
|
export const checkAchievements = (userStats: { |
|
completedModules: number |
|
streak: number |
|
storiesShared: number |
|
flbEarned: number |
|
tribesJoined: number |
|
}): string[] => { |
|
const achievements: string[] = [] |
|
|
|
|
|
if (userStats.completedModules >= 1) { |
|
achievements.push("first-steps") |
|
} |
|
|
|
|
|
if (userStats.streak >= 7) { |
|
achievements.push("streak-starter") |
|
} |
|
|
|
|
|
if (userStats.completedModules >= 10) { |
|
achievements.push("wisdom-seeker") |
|
} |
|
|
|
|
|
if (userStats.tribesJoined >= 1) { |
|
achievements.push("community-builder") |
|
} |
|
|
|
|
|
if (userStats.storiesShared >= 5) { |
|
achievements.push("story-teller") |
|
} |
|
|
|
|
|
if (userStats.flbEarned >= 100) { |
|
achievements.push("flb-collector") |
|
} |
|
|
|
|
|
if (userStats.streak >= 30) { |
|
achievements.push("streak-master") |
|
} |
|
|
|
|
|
if (userStats.storiesShared >= 50) { |
|
achievements.push("ubuntu-spirit") |
|
} |
|
|
|
return achievements |
|
} |
|
|
|
|
|
export const getLevelBenefits = (level: number) => { |
|
return { |
|
flbMultiplier: 1 + (level - 1) * 0.1, |
|
xpMultiplier: 1 + (level - 1) * 0.05, |
|
dailyChallenges: Math.min(3 + Math.floor(level / 5), 10), |
|
specialAccess: level >= 10, |
|
} |
|
} |
|
|
|
|
|
export const calculateProgress = (current: number, target: number): number => { |
|
return Math.min((current / target) * 100, 100) |
|
} |
|
|
|
export const getNextMilestone = (current: number, milestones: number[]): number | null => { |
|
return milestones.find((milestone) => milestone > current) || null |
|
} |
|
|
|
|
|
export const distributeRewards = async ( |
|
walletAddress: string, |
|
flbAmount: number, |
|
xpAmount: number, |
|
achievementIds: string[] = [], |
|
) => { |
|
try { |
|
|
|
console.log(`Distributing rewards to ${walletAddress}:`) |
|
console.log(`- FLB: ${flbAmount}`) |
|
console.log(`- XP: ${xpAmount}`) |
|
console.log(`- Achievements: ${achievementIds.join(", ")}`) |
|
|
|
|
|
const response = await fetch("/api/youth/distribute-rewards", { |
|
method: "POST", |
|
headers: { |
|
"Content-Type": "application/json", |
|
}, |
|
body: JSON.stringify({ |
|
walletAddress, |
|
flbAmount, |
|
xpAmount, |
|
achievementIds, |
|
}), |
|
}) |
|
|
|
if (!response.ok) { |
|
throw new Error("Failed to distribute rewards") |
|
} |
|
|
|
return await response.json() |
|
} catch (error) { |
|
console.error("Error distributing rewards:", error) |
|
throw error |
|
} |
|
} |
|
|