160 lines
6.8 KiB
JavaScript
160 lines
6.8 KiB
JavaScript
import { useMemo } from 'react';
|
|
|
|
const LEVEL_THRESHOLDS = [
|
|
{ level: 1, xp: 0, title: 'Rookie' },
|
|
{ level: 2, xp: 1000, title: 'Scout' },
|
|
{ level: 3, xp: 2500, title: 'Trekker' },
|
|
{ level: 4, xp: 5000, title: 'Pathfinder' },
|
|
{ level: 5, xp: 10000, title: 'Roof Warrior' },
|
|
{ level: 6, xp: 15000, title: 'Storm Chaser' },
|
|
{ level: 7, xp: 25000, title: 'Elite Operator' },
|
|
{ level: 8, xp: 40000, title: 'Neighborhood Boss' },
|
|
{ level: 9, xp: 60000, title: 'Commanding Officer' },
|
|
{ level: 10, xp: 100000, title: 'Legend' }
|
|
];
|
|
|
|
export const useGamification = (user, role = 'FIELD_AGENT') => {
|
|
return useMemo(() => {
|
|
if (!user) return null;
|
|
|
|
const xp = user.xp || 0;
|
|
|
|
// Calculate Level
|
|
let currentLevelObj = LEVEL_THRESHOLDS[0];
|
|
let nextLevelObj = LEVEL_THRESHOLDS[1];
|
|
|
|
for (let i = 0; i < LEVEL_THRESHOLDS.length; i++) {
|
|
if (xp >= LEVEL_THRESHOLDS[i].xp) {
|
|
currentLevelObj = LEVEL_THRESHOLDS[i];
|
|
nextLevelObj = LEVEL_THRESHOLDS[i + 1] || LEVEL_THRESHOLDS[i]; // Max level fallback
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
const { level, title } = currentLevelObj;
|
|
|
|
// XP Progress
|
|
let xpProgress = 100;
|
|
let xpToNext = 0;
|
|
let nextLevelXp = nextLevelObj.xp;
|
|
let currentLevelBaseXp = currentLevelObj.xp;
|
|
|
|
if (level < LEVEL_THRESHOLDS[LEVEL_THRESHOLDS.length - 1].level) {
|
|
const range = nextLevelXp - currentLevelBaseXp;
|
|
const progress = xp - currentLevelBaseXp;
|
|
xpProgress = Math.min(100, Math.max(0, (progress / range) * 100));
|
|
xpToNext = nextLevelXp - xp;
|
|
}
|
|
|
|
// --- PHASE 3: CHECKPOINTS & STAGES ---
|
|
let STAGES = [];
|
|
if (role === 'OWNER') {
|
|
STAGES = [
|
|
{ id: 1, title: "Q1 Launch", requirement: { type: "onboarding", amount: 3 }, reward: { type: "coupon", name: "Territory Expansion", icon: "map" }, progress: 1, goal: 3 },
|
|
{ id: 2, title: "Market Domination", requirement: { type: "revenue", amount: 100 }, reward: { type: "giftcard", name: "Executive Suite Unlock", icon: "star" }, progress: 85, goal: 100 },
|
|
{ id: 3, title: "Empire Builder", requirement: { type: "deals", amount: 50 }, reward: { type: "cash", name: "Profit Share Tier 2", icon: "cash" }, progress: 20, goal: 50 }
|
|
];
|
|
} else if (role === 'ADMIN') {
|
|
STAGES = [
|
|
{ id: 1, title: "Squad Setup", requirement: { type: "recruits", amount: 5 }, reward: { type: "coupon", name: "Team Bonus Unlock", icon: "users" }, progress: 3, goal: 5 },
|
|
{ id: 2, title: "Territory Control", requirement: { type: "regions", amount: 4 }, reward: { type: "giftcard", name: "Admin Power-Up", icon: "zap" }, progress: 4, goal: 4 },
|
|
{ id: 3, title: "Regional Manager", requirement: { type: "sales", amount: 20 }, reward: { type: "cash", name: "Override Commission", icon: "cash" }, progress: 12, goal: 20 }
|
|
];
|
|
} else {
|
|
STAGES = [
|
|
{
|
|
id: 1,
|
|
title: "Daily Grind",
|
|
requirement: { type: "dailyQuests", amount: 5 },
|
|
reward: { type: "xp", name: "+500 XP Boost", icon: "zap" },
|
|
progress: 5,
|
|
goal: 5
|
|
},
|
|
{
|
|
id: 2,
|
|
title: "Door Warrior",
|
|
requirement: { type: "doorsKnocked", amount: 40 },
|
|
reward: { type: "coupon", name: "$25 Gift Card", icon: "gift" },
|
|
progress: 40,
|
|
goal: 40
|
|
},
|
|
{
|
|
id: 3,
|
|
title: "Lead Machine",
|
|
requirement: { type: "leadsGained", amount: 10 },
|
|
reward: { type: "cash", name: "$50 Cash Bonus", icon: "cash" },
|
|
progress: 4,
|
|
goal: 10
|
|
},
|
|
{
|
|
id: 4,
|
|
title: "Closer Mode",
|
|
requirement: { type: "appointmentsSet", amount: 5 },
|
|
reward: { type: "giftcard", name: "$100 Amazon", icon: "amazon" },
|
|
progress: 0,
|
|
goal: 5
|
|
},
|
|
{
|
|
id: 5,
|
|
title: "Legend Run",
|
|
requirement: { type: "deals", amount: 3 },
|
|
reward: { type: "cash", name: "$250 Bonus", icon: "cash" },
|
|
progress: 0,
|
|
goal: 3
|
|
}
|
|
];
|
|
}
|
|
|
|
// Determine lock states
|
|
const stages = STAGES.map((s, idx) => {
|
|
let status = "locked";
|
|
if (s.progress >= s.goal) {
|
|
status = "completed";
|
|
} else if (idx === 0 || STAGES[idx - 1].progress >= STAGES[idx - 1].goal) {
|
|
status = "in-progress";
|
|
}
|
|
return { ...s, status };
|
|
});
|
|
|
|
const activeStage = stages.find(s => s.status === "in-progress") || stages[stages.length - 1];
|
|
|
|
// --- PHASE 3: DETAILED BADGE ARCHITECTURE ---
|
|
// Mapping known string arrays from mockStore into detailed objects
|
|
const BADGE_DEFINITIONS = [
|
|
{ id: 'Hot Spot Hunter', title: "Hot Spot Hunter", desc: "Identify 10 high-damage zones.", criteria: "10 Hot Leads identified." },
|
|
{ id: 'Storm Chaser', title: "Storm Chaser", desc: "Canvass a territory within 48h of a storm.", criteria: "1 Storm deployed operation." },
|
|
{ id: 'Star Closer', title: "Star Closer", desc: "Maintain a 50% conversion rate.", criteria: "Convert 5 deals continuously." },
|
|
{ id: 'Map Master', title: "Map Master", desc: "Knock 500 total doors.", criteria: "500 Lifetime Doors." },
|
|
{ id: 'First Knock', title: "First Knock", desc: "Complete your first day in the field.", criteria: "Log your first shift." }
|
|
];
|
|
|
|
const badges = BADGE_DEFINITIONS.map(def => {
|
|
const isUnlocked = (user.achievements || []).includes(def.id);
|
|
return { ...def, status: isUnlocked ? 'unlocked' : 'locked' };
|
|
});
|
|
|
|
// Derived Stats
|
|
const stats = {
|
|
doorsKnocked: user.doorsKnocked || 0,
|
|
leadsGained: user.leadsGained || 0,
|
|
appointmentsSet: user.appointmentsSet || 0,
|
|
streakDays: user.streakDays || 0,
|
|
};
|
|
|
|
return {
|
|
level,
|
|
title,
|
|
currentXp: xp,
|
|
nextLevelXp,
|
|
xpProgress,
|
|
xpToNext,
|
|
stats,
|
|
badges,
|
|
stages,
|
|
activeStage,
|
|
achievements: user.achievements || [] // keeping for legacy
|
|
};
|
|
}, [user]);
|
|
};
|