import React, { useState, useEffect } from 'react';
import profilePic from '../assets/images/Profile_Pic_1_Agent.png';
import { motion, AnimatePresence } from 'framer-motion';
import { useNavigate } from 'react-router-dom';
import {
Flame, Target, Crosshair, Trophy, Shield, Zap, Star, Medal, Map, Activity, Home,
Users, TrendingUp, CheckCircle, Lock, Award, Calendar, Camera, ChevronRight, Swords,
X, ClipboardList, Handshake, DoorOpen, UserPlus
} from 'lucide-react';
import { useAuth } from '../context/AuthContext';
import { useMockStore } from '../data/mockStore';
import { useGamification as useOldGamification } from '../hooks/useGamification';
import { GamificationProvider, useGamification } from '../context/GamificationContext';
import FloatingXPManager from '../components/ProCanvas/gamification/FloatingXPManager';
import LevelUpModal from '../components/ProCanvas/gamification/LevelUpModal';
import { AnimatedCounter } from '../components/AnimatedCounter';
import { SpotlightCard } from '../components/SpotlightCard';
// ═══════════════════════════════════════════════════════════════════
// DESIGN TOKENS
// ═══════════════════════════════════════════════════════════════════
const C = {
gold: '#fda913', neon: '#AAFF00', fire: '#ff4500', blue: '#3b82f6',
cyan: '#06b6d4', green: '#22c55e', pink: '#ec4899', purple: '#a855f7',
};
// ── Pixel-art SVGs ──
const PixelTrophy = ({ size = 20, color = C.gold, className = '' }) => (
);
const PixelFlame = ({ size = 20, color = C.fire }) => (
);
const PixelBolt = ({ size = 18, color = C.neon }) => (
);
const PixelStar = ({ size = 16, color = C.gold }) => (
);
// ── Corner brackets decoration (like old design) ──
const CornerBrackets = ({ color = C.gold, thickness = 2 }) => (
<>
>
);
// ── Animated floating dots ──
const FloatingDots = ({ color = C.gold, count = 5 }) => (
{Array.from({ length: count }).map((_, i) => (
))}
);
// Pulse ring — smoother timing
const PulseRing = ({ color = C.fire, size = 56 }) => (
{[0, 1, 2].map(i => (
))}
);
// Grid pattern overlay
const GridPattern = ({ color = C.gold, opacity = 0.06 }) => (
);
// CRT scanlines
const ScanlineOverlay = () => (
);
// ═══════════════════════════════════════════════════════════════════
// GAME CARD — the main card primitive (chunky, with corner brackets)
// ═══════════════════════════════════════════════════════════════════
const GameCard = ({ children, className = '', accent = C.gold, scanlines = false, brackets = true, spotlightColor, glow = false, ...props }) => (
{/* Top accent line */}
{/* Bottom accent line */}
{brackets && }
{scanlines && }
{children}
);
// ── Section header — big, bold, like the old design ──
const SectionHeader = ({ icon: Icon, pixelIcon: PIcon, title, color = C.gold, extra = null, size = 'lg' }) => (
);
// ── Progress bar — thick and glowing ──
const ProgressBar = ({ progress, goal, color = C.gold, height = 'h-4', showLabel = true, label = '' }) => {
const pct = Math.min((progress / goal) * 100, 100);
return (
{showLabel && (
{label &&
{label}}
/ {goal}
)}
3 ? 0.9 : 0 }}
/>
);
};
// ── Stagger entrance ──
const containerV = { hidden: {}, show: { transition: { staggerChildren: 0.08, delayChildren: 0.05 } } };
const itemV = { hidden: { opacity: 0, y: 28 }, show: { opacity: 1, y: 0, transition: { type: 'spring', stiffness: 240, damping: 22 } } };
// ═══════════════════════════════════════════════════════════════════
// HOOKS
// ═══════════════════════════════════════════════════════════════════
const useMonthResetCountdown = () => {
const [t, setT] = useState('');
useEffect(() => {
const calc = () => {
const now = new Date(), end = new Date(now.getFullYear(), now.getMonth() + 1, 1);
const diff = end - now; if (diff <= 0) { setT('Resetting...'); return; }
setT(`${Math.floor(diff / 86400000)}d ${String(Math.floor((diff % 86400000) / 3600000)).padStart(2, '0')}h ${String(Math.floor((diff % 3600000) / 60000)).padStart(2, '0')}m`);
};
calc(); const id = setInterval(calc, 60000); return () => clearInterval(id);
}, []); return t;
};
// ═══════════════════════════════════════════════════════════════════
// CONFIG
// ═══════════════════════════════════════════════════════════════════
const RANK_TIERS = {
'Rookie': { color: '#CD7F32' }, 'Field Runner': { color: '#A8A9AD' },
'Roof Warrior': { color: '#fda913' }, 'Territory Titan': { color: '#A855F7' },
'Legendary Closer': { color: '#AAFF00' },
};
const RANK_ORDER = ['Rookie', 'Field Runner', 'Roof Warrior', 'Territory Titan', 'Legendary Closer'];
const nextRank = (c) => { const i = RANK_ORDER.indexOf(c); return i >= 0 && i < RANK_ORDER.length - 1 ? RANK_ORDER[i + 1] : 'MAX RANK'; };
// ═══════════════════════════════════════════════════════════════════
// 1. OPERATOR PROFILE — big chunky player card
// ═══════════════════════════════════════════════════════════════════
const OperatorProfile = ({ gamestate, user, badges = [], className = '' }) => {
const tier = RANK_TIERS[gamestate.currentTitle] || RANK_TIERS['Field Runner'];
const initials = user.name.split(' ').map(n => n[0]).join('').slice(0, 2);
const BADGE_ICONS_MAP = { Hunter: Crosshair, Storm: Zap, Star: Star, Master: Map };
const BADGE_COLORS_MAP = { Hunter: C.fire, Storm: C.gold, Star: C.pink, Master: C.green };
const topBadges = badges.filter(b => b.status === 'unlocked').slice(0, 3);
return (
{/* Card-game style photo frame */}
{/* Outer decorative frame */}
{/* Frame background with gradient border */}
{/* Inner card with photo */}
{user.profilePhoto ? (

) : (
{initials}
)}
{/* Holographic shimmer overlay */}
{/* Corner star accents on frame */}
{/* Streak badge */}
{gamestate.streak > 0 && (
{gamestate.streak}
)}
{/* Name & Rank */}
{user.name.toUpperCase()}
LVL {gamestate.level} — {gamestate.currentTitle}
{/* XP Bar */}
→ {nextRank(gamestate.currentTitle)}
{/* Quick Stats */}
{[
{ icon: Home, val: gamestate.dailyQuests?.knocks?.current || 18, lbl: 'Knocked', color: C.neon },
{ icon: Users, val: gamestate.dailyQuests?.leads?.current || 6, lbl: 'Leads', color: C.gold },
{ icon: Calendar, val: gamestate.dailyQuests?.inspections?.current || 3, lbl: 'Appts', color: C.cyan },
].map(({ icon: I, val, lbl, color }) => (
))}
{/* Top 3 Badges */}
{topBadges.length > 0 && (
Top Badges
{topBadges.map((b, i) => {
const key = Object.keys(BADGE_ICONS_MAP).find(k => b.id?.includes(k));
const Icon = key ? BADGE_ICONS_MAP[key] : Shield;
const bColor = key ? BADGE_COLORS_MAP[key] : C.cyan;
return (
);
})}
)}
);
};
// ═══════════════════════════════════════════════════════════════════
// 2. DAILY MISSIONS — like the old design, chunky quest list
// ═══════════════════════════════════════════════════════════════════
const DailyMissions = ({ gamestate, role, className = '' }) => {
const navigate = useNavigate();
const weeklyKnocks = gamestate.weeklyChallenge?.knocks || { current: 140, target: 150 };
const dailyQuests = [
{ icon: Home, label: 'Door Knocks', current: gamestate.dailyQuests?.knocks?.current || 18, target: gamestate.dailyQuests?.knocks?.target || 20, color: C.blue },
{ icon: Users, label: 'New Leads', current: gamestate.dailyQuests?.leads?.current || 4, target: gamestate.dailyQuests?.leads?.target || 5, color: C.neon },
{ icon: Calendar, label: 'Inspections', current: gamestate.dailyQuests?.inspections?.current || 0, target: gamestate.dailyQuests?.inspections?.target || 1, color: C.gold },
];
return (
{/* Weekly Challenge */}
{/* Daily Quests */}
{dailyQuests.map(({ icon: I, label, current, target, color }) => (
))}
{/* CTA Button — smooth pulsing glow */}
navigate('/emp/fa/maps')}
className="w-full mt-6 py-4 rounded-xl font-['Barlow_Condensed'] font-black text-lg uppercase tracking-wider flex items-center justify-center gap-3 cursor-pointer"
style={{ background: C.neon, color: '#09090b', border: `2px solid ${C.neon}88` }}>
Hit The Map
);
};
// ═══════════════════════════════════════════════════════════════════
// 3. SHOWDOWN & STREAK — side by side stat cards
// ═══════════════════════════════════════════════════════════════════
const ShowdownCard = ({ gamestate }) => (
Neighborhood} />
{[1, 2, 3, 4, 5].map(s => )}
+250 XP
);
const StreakCard = ({ gamestate }) => (
Current Streak
Days
Personal Best: 14 Days
{[1, 2, 3, 4, 5].map(s =>
)}
+50 XP
);
// ═══════════════════════════════════════════════════════════════════
// 4. REWARDS & CHECKPOINTS — named stages with reward labels
// ═══════════════════════════════════════════════════════════════════
const STAGE_NAMES = ['Daily Grind', 'Door Warrior', 'Lead Machine', 'Closer Mode', 'Legend Run'];
const STAGE_REWARDS = ['+500 XP Boost', '$25 Gift Card', '$50 Cash Bonus', '???', '???'];
const RewardsCheckpoints = ({ stages, className = '' }) => {
const completedCount = stages.filter(s => s.status === 'completed').length;
const totalPct = stages.length > 0 ? (completedCount / stages.length) * 100 : 0;
const activeStage = stages.find(s => s.status === 'in-progress') || stages[completedCount] || stages[0];
return (
{Math.round(totalPct)}%} />
{/* Stage timeline */}
{/* Connecting line through all stages */}
{stages.map((stage, idx) => {
const done = stage.status === 'completed', active = stage.status === 'in-progress';
const stageColor = done ? C.gold : active ? C.neon : 'rgb(63 63 70)';
const stageName = STAGE_NAMES[idx] || `Stage ${idx + 1}`;
const stageReward = STAGE_REWARDS[idx] || '???';
return (
{done ? : !done && !active ? :
{idx + 1}}
{stageName}
{done &&
{stage.progress?.current || 0}/{stage.progress?.target || 0} ✓
}
);
})}
{/* Reward tiles */}
{stages.map((stage, idx) => {
const done = stage.status === 'completed', active = stage.status === 'in-progress';
const stageReward = stage.reward?.name || STAGE_REWARDS[idx] || '???';
return (
);
})}
);
};
// ═══════════════════════════════════════════════════════════════════
// 5. BADGES & ACHIEVEMENTS — tall badge cards with descriptions
// ═══════════════════════════════════════════════════════════════════
const BADGE_ICONS = { Hunter: Crosshair, Storm: Zap, Star: Star, Master: Map };
const BADGE_COLORS = { Hunter: C.fire, Storm: C.gold, Star: C.pink, Master: C.green };
const BadgesAchievements = ({ badges, className = '' }) => (
{badges.map((badge, idx) => {
const unlocked = badge.status === 'unlocked';
const key = Object.keys(BADGE_ICONS).find(k => badge.id?.includes(k)) || null;
const Icon = key ? BADGE_ICONS[key] : Shield;
const color = key ? BADGE_COLORS[key] : C.cyan;
return (
{unlocked && (
)}
{badge.title}
{unlocked ? (badge.progressText || 'UNLOCKED') : badge.criteria}
);
})}
);
// ═══════════════════════════════════════════════════════════════════
// 6. LEADERBOARD — full-width, chunky rows
// ═══════════════════════════════════════════════════════════════════
const Leaderboard = ({ className = '' }) => {
const { leaderboard } = useGamification();
const countdown = useMonthResetCountdown();
return (
Resets in
{countdown}
} />
{leaderboard.map((agent, i) => {
const isUser = agent.isUser, rank = i + 1, isTop3 = rank <= 3;
const medalColor = rank === 1 ? C.gold : rank === 2 ? '#A8A9AD' : rank === 3 ? '#CD7F32' : null;
return (
{isTop3 && (
)}
{isTop3 ? (
) :
{rank}.}
{agent.name.split(' ').map(n => n[0]).join('').slice(0, 2)}
{agent.name}
);
})}
);
};
// ═══════════════════════════════════════════════════════════════════
// 7. ADMIN / OWNER WIDGETS
// ═══════════════════════════════════════════════════════════════════
const AdminCommandCenter = ({ className = '' }) => (
Season Ops} />
{[
{ label: 'Active Squads', value: 12, sub: 'All Online', color: C.cyan },
{ label: 'Doors Today', value: 1432, sub: '+14% vs Yesterday', color: C.gold },
{ label: 'Approval Queue', value: 8, sub: 'Needs Review', color: C.fire },
].map(({ label, value, sub, color }) => (
))}
);
const OwnerROIDashboard = ({ className = '' }) => (
Cost YTD: $} />
{[
{ label: 'Participation', value: '92%', color: C.green },
{ label: 'Prize Fund', value: '68%', color: C.gold },
{ label: 'Conversion Lift', value: '+24%', color: C.green },
{ label: 'Revenue', value: '$1.2M', color: C.gold },
].map(({ label, value, color }) => (
))}
);
// ═══════════════════════════════════════════════════════════════════
// 8. LOG ACTION CARD + MODAL
// ═══════════════════════════════════════════════════════════════════
const LOG_ACTIONS = [
{
key: 'knock', label: 'Door Knocked', icon: DoorOpen, color: C.cyan,
fields: [
{ name: 'address', label: 'Address', type: 'text', placeholder: '123 Main St' },
{ name: 'outcome', label: 'Outcome', type: 'select', options: ['Interested', 'Not Home', 'Not Interested', 'Follow Up'] },
{ name: 'notes', label: 'Notes', type: 'textarea', placeholder: 'Any details...' },
]
},
{
key: 'lead', label: 'Lead Gained', icon: UserPlus, color: C.neon,
fields: [
{ name: 'name', label: 'Contact Name', type: 'text', placeholder: 'John Doe' },
{ name: 'phone', label: 'Phone', type: 'text', placeholder: '(555) 123-4567' },
{ name: 'email', label: 'Email', type: 'text', placeholder: 'john@example.com' },
{ name: 'address', label: 'Property Address', type: 'text', placeholder: '456 Oak Ave' },
{ name: 'notes', label: 'Notes', type: 'textarea', placeholder: 'Lead source & details...' },
]
},
{
key: 'appt', label: 'Appointment Set', icon: Calendar, color: C.gold,
fields: [
{ name: 'clientName', label: 'Client Name', type: 'text', placeholder: 'Jane Smith' },
{ name: 'date', label: 'Date & Time', type: 'datetime-local' },
{ name: 'address', label: 'Address', type: 'text', placeholder: '789 Pine Rd' },
{ name: 'type', label: 'Type', type: 'select', options: ['Roof Inspection', 'Estimate', 'Follow Up', 'Other'] },
{ name: 'notes', label: 'Notes', type: 'textarea', placeholder: 'Meeting details...' },
]
},
{
key: 'meeting', label: 'Client Meeting', icon: Handshake, color: C.purple,
fields: [
{ name: 'clientName', label: 'Client Name', type: 'text', placeholder: 'Client name' },
{ name: 'outcome', label: 'Outcome', type: 'select', options: ['Deal Closed', 'Follow Up Needed', 'Proposal Sent', 'No Deal'] },
{ name: 'followUp', label: 'Follow-up Date', type: 'date' },
{ name: 'notes', label: 'Notes', type: 'textarea', placeholder: 'Meeting summary...' },
]
},
];
const LogActionCard = ({ className = '', onAction }) => (
{LOG_ACTIONS.map(({ key, label, icon: I, color }) => (
onAction(key)}
className="rounded-xl p-4 flex flex-col items-center justify-center gap-2.5 cursor-pointer bg-zinc-100 dark:bg-white/[0.03] border border-zinc-200 dark:border-white/[0.06] hover:border-white/15 transition-colors">
{label}
))}
);
const LogActionModal = ({ actionKey, onClose }) => {
const config = LOG_ACTIONS.find(a => a.key === actionKey);
const [form, setForm] = useState({});
if (!config) return null;
const Icon = config.icon;
const handleSubmit = (e) => {
e.preventDefault();
// In production: dispatch to API
onClose();
};
return (
e.stopPropagation()}
className="relative w-full max-w-lg rounded-2xl overflow-hidden bg-zinc-50 dark:bg-zinc-950 border border-zinc-200 dark:border-white/10 shadow-2xl">
{/* Header */}
{/* Form */}
);
};
// ═══════════════════════════════════════════════════════════════════
// 9. CHALLENGES MODAL
// ═══════════════════════════════════════════════════════════════════
const ChallengesModal = ({ onClose, gamestate }) => {
const challenges = [
{ title: 'Weekly Knock Target', desc: 'Hit 150 doors this week', current: gamestate.weeklyChallenge?.knocks?.current || 140, target: 150, color: C.gold, reward: '+500 XP', icon: Home },
{ title: 'Daily Lead Master', desc: 'Generate 5 leads today', current: gamestate.dailyQuests?.leads?.current || 4, target: 5, color: C.neon, reward: '+100 XP', icon: UserPlus },
{ title: 'Inspection Sprint', desc: 'Complete 1 inspection today', current: gamestate.dailyQuests?.inspections?.current || 0, target: 1, color: C.cyan, reward: '+75 XP', icon: Calendar },
{ title: 'Door Warrior', desc: 'Knock 20 doors in a single day', current: gamestate.dailyQuests?.knocks?.current || 18, target: 20, color: C.blue, reward: '+200 XP', icon: DoorOpen },
{ title: 'Streak Keeper', desc: 'Maintain your streak for 7 days', current: gamestate.streak || 5, target: 7, color: C.fire, reward: '+300 XP', icon: Flame },
{ title: 'Photo Hunter', desc: 'Upload 5 roof photos today', current: 3, target: 5, color: C.green, reward: '+150 XP', icon: Camera },
];
return (
e.stopPropagation()}
className="relative w-full max-w-2xl max-h-[80vh] rounded-2xl overflow-hidden bg-zinc-50 dark:bg-zinc-950 border border-zinc-200 dark:border-white/10 shadow-2xl flex flex-col">
{/* Header */}
{/* Content */}
{challenges.map((ch, idx) => {
const pct = Math.min((ch.current / ch.target) * 100, 100);
const done = pct >= 100;
const Icon = ch.icon;
return (
{done ? '✓ DONE' : ch.reward}
{ch.current} / {ch.target}
{Math.round(pct)}%
);
})}
);
};
// ═══════════════════════════════════════════════════════════════════
// 10. ACHIEVEMENTS MODAL
// ═══════════════════════════════════════════════════════════════════
const BADGE_ICONS_GLOBAL = { Hunter: Crosshair, Storm: Zap, Star: Star, Master: Map };
const BADGE_COLORS_GLOBAL = { Hunter: C.fire, Storm: C.gold, Star: C.pink, Master: C.green };
const AchievementsModal = ({ onClose, badges }) => (
e.stopPropagation()}
className="relative w-full max-w-2xl max-h-[80vh] rounded-2xl overflow-hidden bg-zinc-50 dark:bg-zinc-950 border border-zinc-200 dark:border-white/10 shadow-2xl flex flex-col">
{/* Header */}
All Achievements
{badges.filter(b => b.status === 'unlocked').length}/{badges.length} Unlocked
{/* Content */}
{badges.map((badge, idx) => {
const unlocked = badge.status === 'unlocked';
const key = Object.keys(BADGE_ICONS_GLOBAL).find(k => badge.id?.includes(k));
const Icon = key ? BADGE_ICONS_GLOBAL[key] : Shield;
const color = key ? BADGE_COLORS_GLOBAL[key] : C.cyan;
return (
{badge.title}
{unlocked &&
}
{badge.criteria || 'Complete the challenge to unlock'}
{badge.progressText &&
{badge.progressText}
}
{unlocked ? 'UNLOCKED' : 'LOCKED'}
);
})}
);
// ═══════════════════════════════════════════════════════════════════
// MAIN LAYOUT — two-column bento, no bottom ribbon
// ═══════════════════════════════════════════════════════════════════
function ProCanvasContent() {
const { user } = useAuth();
const { users } = useMockStore();
const role = user?.role || 'FIELD_AGENT';
const mockDbUser = {
...(users.find(u => u.email === user?.email) || users.find(u => u.roles?.includes('FIELD_AGENT')) || users.find(u => u.type === 'employee') || {
name: user?.name || 'Agent Unknown', empId: 'OP-00', xp: 12450, doorsKnocked: 342, leadsGained: 45,
appointmentsSet: 18, streakDays: 5, achievements: ['Hot Spot Hunter', 'Storm Chaser'],
}),
profilePhoto: profilePic,
};
const oldGamestate = useOldGamification(mockDbUser);
const gs = useGamification();
// Modal state
const [logAction, setLogAction] = useState(null); // 'knock' | 'lead' | 'appt' | 'meeting' | null
const [showChallenges, setShowChallenges] = useState(false);
const [showAchievements, setShowAchievements] = useState(false);
if (!oldGamestate || !gs) return (
);
return (
{/* Ambient glows */}
{/* ── Modals ── */}
{logAction && setLogAction(null)} />}
{showChallenges && setShowChallenges(false)} gamestate={gs} />}
{showAchievements && setShowAchievements(false)} badges={oldGamestate.badges} />}
{/* ══ HEADER ══ */}
{[{ label: 'Leaderboard', color: C.gold, onClick: () => { const el = document.getElementById('procanvas-leaderboard'); el?.scrollIntoView({ behavior: 'smooth' }); } },
{ label: 'Challenges', color: C.neon, onClick: () => setShowChallenges(true) },
{ label: 'Achievements', color: C.gold, onClick: () => setShowAchievements(true) },
].map((tab, i) => (
{tab.label}
))}
{mockDbUser.name?.split(' ')[0]}
XP
{/* ══ TWO-COLUMN LAYOUT ══ */}
{/* Left: Player Card + Daily Missions (sticky sidebar, 4 cols) */}
{/* Right: Main content (8 cols) */}
{/* Showdown + Streak (side by side) */}
{/* Rewards & Checkpoints (full width) */}
{/* Badges & Achievements (full width, clickable) */}
setShowAchievements(true)} className="cursor-pointer">
{/* Leaderboard (full width) */}
{/* Admin/Owner */}
{role === 'OWNER' &&
}
{(role === 'ADMIN' || role === 'OWNER') &&
}
);
}
export default function ProCanvas() {
return (
);
}