feat(ui): refactor gamification UI for role awareness and remove floaters
This commit is contained in:
+212
-69
@@ -1,14 +1,17 @@
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Flame, Target, Crosshair, Trophy, UploadCloud,
|
||||
Shield, Zap, Star, Medal, Map, Activity,
|
||||
Users, TrendingUp, CheckCircle, Lock, FastForward, Award, Calendar
|
||||
Shield, Zap, Star, Medal, Map, Activity, Home,
|
||||
Users, TrendingUp, CheckCircle, Lock, FastForward, Award, Calendar, ClipboardCheck
|
||||
} from 'lucide-react';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { useMockStore } from '../data/mockStore';
|
||||
import { useGamification } from '../hooks/useGamification';
|
||||
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 { SpotlightCard } from '../components/SpotlightCard';
|
||||
import { AnimatedCounter } from '../components/AnimatedCounter';
|
||||
|
||||
@@ -73,21 +76,37 @@ const OperatorProfile = ({ gamestate, user, className = "" }) => {
|
||||
<div className="flex-1 text-center md:text-left z-10 w-full flex flex-col justify-center">
|
||||
<div className="flex justify-between items-start mb-4 gap-4 md:gap-8 w-full">
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-[10px] font-mono text-zinc-500 tracking-[0.2em] mb-1 uppercase truncate">Op ID: {user.empId || 'UNK-99'}</p>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<p className="text-[10px] font-mono text-zinc-500 tracking-[0.2em] uppercase truncate">Op ID: {user.empId || 'UNK-99'}</p>
|
||||
<div className="flex items-center gap-1 bg-orange-500/10 text-orange-500 px-2 py-0.5 rounded-full border border-orange-500/20">
|
||||
<motion.div animate={{ scale: [1, 1.2, 1] }} transition={{ repeat: Infinity, duration: 2 }}>
|
||||
<Flame size={12} className="fill-current drop-shadow-[0_0_5px_rgba(249,115,22,0.8)]" />
|
||||
</motion.div>
|
||||
<span className="text-[10px] font-bold font-mono">{gamestate.streak} Day Streak</span>
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="text-2xl md:text-3xl font-black text-white tracking-wide truncate">{user.name}</h2>
|
||||
</div>
|
||||
<div className="flex flex-col items-end flex-shrink-0">
|
||||
<span className={`text-xl font-black ${NEON_GOLD}`}>LVL <AnimatedCounter to={gamestate.level} duration={1} /></span>
|
||||
<span className="text-xs font-mono text-zinc-400 tracking-widest uppercase whitespace-nowrap">{gamestate.title}</span>
|
||||
<span className="text-xs font-mono text-zinc-400 tracking-widest uppercase whitespace-nowrap">{gamestate.currentTitle}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full mt-2">
|
||||
<div className="flex justify-between text-xs font-mono mb-2">
|
||||
<span className="text-zinc-400">XP <span className="text-white font-bold"><AnimatedCounter to={gamestate.currentXp} /></span></span>
|
||||
<span className="text-zinc-500">NEXT <span className="text-zinc-300">{gamestate.nextLevelXp.toLocaleString()}</span></span>
|
||||
<span className="text-zinc-400">XP <span className="text-white font-bold"><AnimatedCounter to={gamestate.xp} /></span></span>
|
||||
<span className="text-zinc-500">NEXT <span className="text-zinc-300">{gamestate.nextLevelXP.toLocaleString()}</span></span>
|
||||
</div>
|
||||
{/* Using context's exact progressPercent for precision inside the progress bar */}
|
||||
<div className="h-2 w-full bg-black/80 rounded-full shadow-[inset_0_2px_4px_rgba(0,0,0,0.6)] border border-white/5 relative overflow-hidden flex-shrink-0">
|
||||
<motion.div
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: `${gamestate.progressPercent}%` }}
|
||||
transition={{ type: "spring", stiffness: 50, damping: 15 }}
|
||||
className={`absolute top-0 left-0 h-full rounded-full bg-gradient-to-r from-amber-600 to-[#fda913] shadow-[0_0_15px_#fda913]`}
|
||||
/>
|
||||
</div>
|
||||
<ProgressBar progress={gamestate.currentXp} goal={gamestate.nextLevelXp} colorClass="bg-gradient-to-r from-amber-600 to-[#fda913]" shadowClass="shadow-[0_0_15px_#fda913]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -95,40 +114,114 @@ const OperatorProfile = ({ gamestate, user, className = "" }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const LiveMissionTracker = ({ stage, className = "" }) => {
|
||||
const TacticalMissions = ({ role, gamestate, className = "" }) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Role-based content adaptations
|
||||
let headerTitle = "Daily Quests";
|
||||
let weeklyTitle = "Weekly Challenge";
|
||||
let weeklyTargetLabel = "Total Knocks";
|
||||
|
||||
if (role === 'ADMIN') {
|
||||
headerTitle = "Squad Directives";
|
||||
weeklyTitle = "Global Target";
|
||||
weeklyTargetLabel = "Global Knocks";
|
||||
} else if (role === 'OWNER') {
|
||||
headerTitle = "Key Objectives";
|
||||
weeklyTitle = "Revenue Drivers";
|
||||
weeklyTargetLabel = "Required Activity";
|
||||
}
|
||||
|
||||
const MissionBar = ({ current, target, colorClass, label, icon: Icon }) => {
|
||||
const pct = Math.min((current / target) * 100, 100);
|
||||
const isComplete = current >= target;
|
||||
|
||||
return (
|
||||
<div className="mb-4 last:mb-0 w-full">
|
||||
<div className="flex justify-between items-center mb-1.5">
|
||||
<div className="flex items-center gap-2 text-[10px] md:text-xs font-mono font-medium text-zinc-400">
|
||||
<Icon className={`w-3.5 h-3.5 ${isComplete ? colorClass : 'text-zinc-500'}`} />
|
||||
<span>{label}</span>
|
||||
</div>
|
||||
<div className={`text-[10px] md:text-xs font-bold font-mono ${isComplete ? colorClass : 'text-white'}`}>
|
||||
<AnimatedCounter to={current} /> / {target}
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-1.5 w-full bg-black/80 rounded-full shadow-[inset_0_2px_4px_rgba(0,0,0,0.6)] border border-white/5 relative overflow-hidden flex-shrink-0">
|
||||
<motion.div
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: `${pct}%` }}
|
||||
transition={{ duration: 1.5, type: "spring", stiffness: 50, damping: 15 }}
|
||||
className={`absolute top-0 left-0 h-full rounded-full ${isComplete ? 'bg-[#39ff14] shadow-[0_0_10px_#39ff14]' : `bg-gradient-to-r ${colorClass}`}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<NeoCard spotlightColor="rgba(57, 255, 20, 0.1)" className={className} innerClassName="justify-between h-full">
|
||||
<div className="flex flex-col h-full w-full">
|
||||
<div className="flex items-center gap-3 mb-6 flex-shrink-0">
|
||||
<Crosshair className={NEON_GREEN} size={24} />
|
||||
<h3 className="text-sm md:text-base font-mono font-bold text-white tracking-widest uppercase">Live Mission Tracker</h3>
|
||||
<h3 className="text-sm md:text-base font-mono font-bold text-white tracking-widest uppercase pb-1">Tactical Missions</h3>
|
||||
</div>
|
||||
|
||||
<div className="mb-4 flex-shrink-0">
|
||||
<p className="text-[10px] text-zinc-500 font-mono tracking-widest mb-1 uppercase">ACTIVE DIRECTIVE</p>
|
||||
<p className="text-lg md:text-xl text-white font-bold mb-1 truncate">{stage.title}</p>
|
||||
<p className="text-xs md:text-sm text-zinc-400 font-mono truncate">
|
||||
Target: {stage.goal} {stage.requirement.type.replace(/([A-Z])/g, ' $1').trim()}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3 p-4 bg-black/30 rounded-xl shadow-[inset_0_2px_10px_rgba(0,0,0,0.4)] border border-white/5 mb-6 flex-shrink-0">
|
||||
<div className="flex justify-between font-mono text-xs">
|
||||
<span className={NEON_GREEN}>PROGRESS</span>
|
||||
<span className="text-white font-bold"><AnimatedCounter to={stage.progress} /> / {stage.goal}</span>
|
||||
<div className="flex-1 space-y-6 flex flex-col justify-center min-h-0">
|
||||
{/* Weekly Challenge Component */}
|
||||
<div className="p-3 bg-black/30 rounded-xl border border-[#fda913]/20 shadow-[0_0_15px_rgba(253,169,19,0.05)] w-full">
|
||||
<div className="flex items-center justify-between mb-3 border-b border-white/5 pb-2">
|
||||
<span className="text-[10px] font-mono text-[#fda913] tracking-widest uppercase flex items-center gap-1.5">
|
||||
<Trophy size={12} className="text-[#fda913]" /> {weeklyTitle}
|
||||
</span>
|
||||
</div>
|
||||
<MissionBar
|
||||
current={gamestate.weeklyChallenge?.knocks?.current || 0}
|
||||
target={gamestate.weeklyChallenge?.knocks?.target || 1}
|
||||
colorClass="from-amber-600 to-[#fda913]"
|
||||
label={weeklyTargetLabel}
|
||||
icon={Target}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Daily Quests */}
|
||||
<div className="p-3 bg-black/30 rounded-xl border border-[#00f0ff]/20 shadow-[0_0_15px_rgba(0,240,255,0.05)] w-full">
|
||||
<div className="flex items-center justify-between mb-3 border-b border-white/5 pb-2">
|
||||
<span className="text-[10px] font-mono text-[#00f0ff] tracking-widest uppercase flex items-center gap-1.5">
|
||||
<Zap size={12} className="text-[#00f0ff]" /> {headerTitle}
|
||||
</span>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{role === 'ADMIN' ? (
|
||||
<>
|
||||
<MissionBar current={gamestate.dailyQuests?.knocks?.current || 0} target={gamestate.dailyQuests?.knocks?.target || 1} colorClass="from-blue-600 to-[#00f0ff]" label="Active Recruits" icon={Users} />
|
||||
<MissionBar current={gamestate.dailyQuests?.leads?.current || 0} target={gamestate.dailyQuests?.leads?.target || 1} colorClass="from-emerald-600 to-[#39ff14]" label="Territories Mapped" icon={Map} />
|
||||
<MissionBar current={gamestate.dailyQuests?.inspections?.current || 0} target={gamestate.dailyQuests?.inspections?.target || 1} colorClass="from-purple-600 to-pink-500" label="Squad Reviews" icon={ClipboardCheck} />
|
||||
</>
|
||||
) : role === 'OWNER' ? (
|
||||
<>
|
||||
<MissionBar current={gamestate.dailyQuests?.knocks?.current || 0} target={gamestate.dailyQuests?.knocks?.target || 1} colorClass="from-amber-600 to-[#fda913]" label="Capital Deployed" icon={TrendingUp} />
|
||||
<MissionBar current={gamestate.dailyQuests?.leads?.current || 0} target={gamestate.dailyQuests?.leads?.target || 1} colorClass="from-emerald-600 to-[#39ff14]" label="New Franchises" icon={Map} />
|
||||
<MissionBar current={gamestate.dailyQuests?.inspections?.current || 0} target={gamestate.dailyQuests?.inspections?.target || 1} colorClass="from-purple-600 to-pink-500" label="Exec Meetings" icon={Users} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<MissionBar current={gamestate.dailyQuests?.knocks?.current || 0} target={gamestate.dailyQuests?.knocks?.target || 1} colorClass="from-blue-600 to-[#00f0ff]" label="Door Knocks" icon={Home} />
|
||||
<MissionBar current={gamestate.dailyQuests?.leads?.current || 0} target={gamestate.dailyQuests?.leads?.target || 1} colorClass="from-emerald-600 to-[#39ff14]" label="New Leads" icon={Users} />
|
||||
<MissionBar current={gamestate.dailyQuests?.inspections?.current || 0} target={gamestate.dailyQuests?.inspections?.target || 1} colorClass="from-purple-600 to-pink-500" label="Inspections" icon={ClipboardCheck} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<ProgressBar progress={stage.progress} goal={stage.goal} />
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => navigate('/emp/fa/maps')}
|
||||
className="w-full group mt-auto relative overflow-hidden rounded-xl bg-zinc-900 border border-[#39ff14]/30 p-4 shadow-[4px_4px_15px_rgba(0,0,0,0.5),-4px_-4px_15px_rgba(255,255,255,0.02)] transition-all hover:-translate-y-1 hover:shadow-[#39ff14]/20 flex-shrink-0"
|
||||
className="w-full group mt-6 relative overflow-hidden rounded-xl bg-zinc-900 border border-[#39ff14]/30 p-4 shadow-[4px_4px_15px_rgba(0,0,0,0.5),-4px_-4px_15px_rgba(255,255,255,0.02)] transition-all hover:-translate-y-1 hover:shadow-[#39ff14]/20 flex-shrink-0"
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-[#39ff14]/10 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||
<span className={`relative z-10 font-mono font-bold tracking-[0.2em] text-sm md:text-base uppercase flex justify-center w-full ${NEON_GREEN}`}>
|
||||
[ GOCANVAS ]
|
||||
[ ENGAGE MAP ]
|
||||
</span>
|
||||
<span className="absolute inset-0 border border-[#39ff14] rounded-xl animate-ping opacity-10" />
|
||||
</button>
|
||||
@@ -137,12 +230,16 @@ const LiveMissionTracker = ({ stage, className = "" }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const CheckpointsSystem = ({ stages, className = "" }) => {
|
||||
const CheckpointsSystem = ({ stages, role, className = "" }) => {
|
||||
let headerTitle = "Rewards & Checkpoints";
|
||||
if (role === 'ADMIN') headerTitle = "Squad Milestone Tracking";
|
||||
if (role === 'OWNER') headerTitle = "Program Milestones";
|
||||
|
||||
return (
|
||||
<NeoCard spotlightColor="rgba(0, 240, 255, 0.05)" className={className}>
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<FastForward className={NEON_BLUE} size={24} />
|
||||
<h3 className="text-lg font-mono font-bold text-white tracking-widest uppercase">Rewards & Checkpoints</h3>
|
||||
<h3 className="text-lg font-mono font-bold text-white tracking-widest uppercase">{headerTitle}</h3>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 relative w-full flex-1">
|
||||
@@ -226,12 +323,7 @@ const DetailedBadges = ({ badges, className = "" }) => {
|
||||
};
|
||||
|
||||
const Leaderboard = ({ title = "Live Intel Feed", icon: Icon = Target, className = "" }) => {
|
||||
const leaders = [
|
||||
{ rank: 1, name: "Felicity Fast", xp: 15600, delta: "+450", role: "Agent" },
|
||||
{ rank: 2, name: "Frank Agent", xp: 12450, delta: "+310", role: "Agent" },
|
||||
{ rank: 3, name: "Fiona Field", xp: 8200, delta: "+120", role: "Agent" },
|
||||
{ rank: 4, name: "Felix Fixer", xp: 5400, delta: "+50", role: "Agent" },
|
||||
];
|
||||
const { leaderboard } = useGamification();
|
||||
|
||||
return (
|
||||
<NeoCard className={className} innerClassName="flex flex-col h-full">
|
||||
@@ -241,28 +333,53 @@ const Leaderboard = ({ title = "Live Intel Feed", icon: Icon = Target, className
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 flex-1 flex flex-col justify-center">
|
||||
{leaders.map((agent, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`group relative flex items-center justify-between p-3 md:p-4 rounded-xl transition-all duration-300
|
||||
${agent.rank <= 3 ? 'bg-black/40 shadow-[inset_0_2px_10px_rgba(0,0,0,0.5)] border border-white/5' : 'bg-transparent hover:bg-white/5'}`}
|
||||
>
|
||||
<div className="flex items-center gap-3 md:gap-4 flex-1 min-w-0">
|
||||
<span className={`font-mono font-bold w-6 text-lg md:text-xl flex-shrink-0 ${agent.rank === 1 ? NEON_GOLD : agent.rank === 2 ? 'text-zinc-300' : agent.rank === 3 ? 'text-amber-700' : 'text-zinc-600'}`}>
|
||||
0{agent.rank}
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<span className="font-bold text-white text-sm md:text-base block truncate">{agent.name}</span>
|
||||
<span className="text-[9px] md:text-[10px] text-zinc-500 uppercase font-mono tracking-widest block truncate">{agent.role}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-end gap-1 font-mono text-xs md:text-sm flex-shrink-0">
|
||||
<span className="text-white font-bold"><AnimatedCounter to={agent.xp} /> XP</span>
|
||||
<span className="text-[#39ff14] text-[9px] md:text-[10px] tracking-wider">{agent.delta} Today</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex-1 overflow-hidden relative min-h-[300px]">
|
||||
<motion.ul layout className="flex flex-col gap-3 relative h-full">
|
||||
<AnimatePresence>
|
||||
{leaderboard.map((agent, i) => {
|
||||
const isUser = agent.isUser;
|
||||
const isTop3 = i < 3;
|
||||
const rank = i + 1;
|
||||
|
||||
return (
|
||||
<motion.li
|
||||
key={agent.id}
|
||||
layout
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.95 }}
|
||||
transition={{ type: "spring", stiffness: 300, damping: 30 }}
|
||||
className={`group relative flex items-center justify-between p-3 md:p-4 rounded-xl transition-all duration-300
|
||||
${isUser
|
||||
? 'bg-blue-600/20 border border-blue-500/50 shadow-[0_0_15px_rgba(59,130,246,0.2)]'
|
||||
: isTop3
|
||||
? 'bg-black/40 shadow-[inset_0_2px_10px_rgba(0,0,0,0.5)] border border-white/5'
|
||||
: 'bg-transparent border border-transparent hover:bg-white/5 border-b-white/5'}`}
|
||||
>
|
||||
{isUser && (
|
||||
<motion.div
|
||||
className="absolute inset-0 bg-gradient-to-r from-blue-500/0 via-blue-500/10 to-blue-500/0 pointer-events-none rounded-xl"
|
||||
animate={{ x: ['-200%', '200%'] }}
|
||||
transition={{ repeat: Infinity, duration: 3, ease: 'linear' }}
|
||||
/>
|
||||
)}
|
||||
<div className="flex items-center gap-3 md:gap-4 flex-1 min-w-0 z-10 relative">
|
||||
<span className={`font-mono font-bold w-6 text-lg md:text-xl flex-shrink-0 ${rank === 1 ? NEON_GOLD : rank === 2 ? 'text-zinc-300' : rank === 3 ? 'text-amber-700' : 'text-zinc-600'}`}>
|
||||
0{rank}
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<span className={`font-bold text-sm md:text-base block truncate ${isUser ? 'text-blue-400' : 'text-white'}`}>{agent.name}</span>
|
||||
<span className="text-[9px] md:text-[10px] text-zinc-500 uppercase font-mono tracking-widest block truncate">{agent.role}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-end gap-1 font-mono text-xs md:text-sm flex-shrink-0 z-10 relative">
|
||||
<span className={`font-bold ${isUser ? 'text-white' : 'text-zinc-300'}`}><AnimatedCounter to={agent.points} /> XP</span>
|
||||
</div>
|
||||
</motion.li>
|
||||
);
|
||||
})}
|
||||
</AnimatePresence>
|
||||
</motion.ul>
|
||||
</div>
|
||||
</NeoCard>
|
||||
);
|
||||
@@ -371,6 +488,8 @@ const OwnerROIDashboard = ({ className = "" }) => {
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
const QuickLogAction = ({ className = "", gridClassName = "grid grid-cols-2 gap-3 flex-1" }) => {
|
||||
const { logKnock, logLead, logInspection } = useGamification();
|
||||
|
||||
return (
|
||||
<NeoCard spotlightColor="rgba(57, 255, 20, 0.1)" className={className} innerClassName="flex flex-col h-full">
|
||||
<div className="flex items-center gap-3 mb-4 flex-shrink-0">
|
||||
@@ -378,15 +497,25 @@ const QuickLogAction = ({ className = "", gridClassName = "grid grid-cols-2 gap-
|
||||
<h3 className="text-sm md:text-base font-mono font-bold text-white tracking-widest uppercase">Quick Operations Log</h3>
|
||||
</div>
|
||||
<div className={`w-full ${gridClassName}`}>
|
||||
<button className="h-full p-1.5 md:p-2 rounded-xl bg-black/40 border border-white/5 hover:bg-zinc-800 hover:border-[#39ff14]/50 hover:shadow-[0_0_15px_rgba(57,255,20,0.2)] transition-all flex flex-col items-center justify-center gap-1 md:gap-2 group cursor-crosshair min-h-0">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
// Simulate High Value Target 25% of the time, or hold shift key
|
||||
const isHighValue = e.shiftKey || Math.random() > 0.75;
|
||||
logKnock(isHighValue, { x: e.clientX, y: e.clientY });
|
||||
}}
|
||||
className="h-full p-1.5 md:p-2 rounded-xl bg-black/40 border border-white/5 hover:bg-zinc-800 hover:border-[#39ff14]/50 hover:shadow-[0_0_15px_rgba(57,255,20,0.2)] transition-all flex flex-col items-center justify-center gap-1 md:gap-2 group cursor-crosshair min-h-0">
|
||||
<Map size={18} className="text-zinc-500 group-hover:text-[#39ff14] transition-colors flex-shrink-0" />
|
||||
<span className="text-[9px] md:text-[10px] text-center font-mono text-zinc-400 group-hover:text-white transition-colors truncate w-full px-1">Door Knock</span>
|
||||
</button>
|
||||
<button className="h-full p-1.5 md:p-2 rounded-xl bg-black/40 border border-white/5 hover:bg-zinc-800 hover:border-[#fda913]/50 hover:shadow-[0_0_15px_rgba(253,169,19,0.2)] transition-all flex flex-col items-center justify-center gap-1 md:gap-2 group cursor-crosshair min-h-0">
|
||||
<button
|
||||
onClick={(e) => logLead({ x: e.clientX, y: e.clientY })}
|
||||
className="h-full p-1.5 md:p-2 rounded-xl bg-black/40 border border-white/5 hover:bg-zinc-800 hover:border-[#fda913]/50 hover:shadow-[0_0_15px_rgba(253,169,19,0.2)] transition-all flex flex-col items-center justify-center gap-1 md:gap-2 group cursor-crosshair min-h-0">
|
||||
<Users size={18} className="text-zinc-500 group-hover:text-[#fda913] transition-colors flex-shrink-0" />
|
||||
<span className="text-[9px] md:text-[10px] text-center font-mono text-zinc-400 group-hover:text-white transition-colors truncate w-full px-1">Lead Gained</span>
|
||||
</button>
|
||||
<button className="h-full p-1.5 md:p-2 rounded-xl bg-black/40 border border-white/5 hover:bg-zinc-800 hover:border-[#00f0ff]/50 hover:shadow-[0_0_15px_rgba(0,240,255,0.2)] transition-all flex flex-col items-center justify-center gap-1 md:gap-2 group cursor-crosshair min-h-0">
|
||||
<button
|
||||
onClick={(e) => logInspection({ x: e.clientX, y: e.clientY })}
|
||||
className="h-full p-1.5 md:p-2 rounded-xl bg-black/40 border border-white/5 hover:bg-zinc-800 hover:border-[#00f0ff]/50 hover:shadow-[0_0_15px_rgba(0,240,255,0.2)] transition-all flex flex-col items-center justify-center gap-1 md:gap-2 group cursor-crosshair min-h-0">
|
||||
<Calendar size={18} className="text-zinc-500 group-hover:text-[#00f0ff] transition-colors flex-shrink-0" />
|
||||
<span className="text-[9px] md:text-[10px] text-center font-mono text-zinc-400 group-hover:text-white transition-colors truncate w-full px-1">Appt Set</span>
|
||||
</button>
|
||||
@@ -415,7 +544,7 @@ const ScanUpload = ({ className = "" }) => {
|
||||
// MAIN COMPONENT EXPORT
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
export default function ProCanvas() {
|
||||
function ProCanvasContent() {
|
||||
const { user } = useAuth();
|
||||
const { users } = useMockStore();
|
||||
|
||||
@@ -433,12 +562,18 @@ export default function ProCanvas() {
|
||||
achievements: ['Hot Spot Hunter', 'Storm Chaser']
|
||||
};
|
||||
|
||||
const gamestate = useGamification(mockDbUser);
|
||||
const oldGamestate = useOldGamification(mockDbUser);
|
||||
const newGamestate = useGamification();
|
||||
|
||||
if (!gamestate) return <div className="p-8 text-white font-mono flex items-center justify-center h-screen bg-[#050505]">INITIALIZING HUD...</div>;
|
||||
if (!oldGamestate || !newGamestate) return <div className="p-8 text-white font-mono flex items-center justify-center h-screen bg-[#050505]">INITIALIZING HUD...</div>;
|
||||
|
||||
return (
|
||||
<div className="min-h-full bg-[#09090b] p-4 md:p-8 overflow-y-auto selection:bg-[#fda913] selection:text-black">
|
||||
<div className="min-h-full bg-[#09090b] p-4 md:p-8 overflow-y-auto selection:bg-[#fda913] selection:text-black relative">
|
||||
|
||||
{/* New Gamification Overlay Components */}
|
||||
<FloatingXPManager />
|
||||
<LevelUpModal />
|
||||
|
||||
{/* Hex Grid Background & Scanlines */}
|
||||
<div className="fixed inset-0 pointer-events-none z-0">
|
||||
<div className="absolute inset-0 opacity-20">
|
||||
@@ -476,15 +611,15 @@ export default function ProCanvas() {
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 md:gap-8 w-full items-stretch">
|
||||
|
||||
{/* Unified Top Row */}
|
||||
<OperatorProfile gamestate={gamestate} user={mockDbUser} className="lg:col-span-2" />
|
||||
<LiveMissionTracker stage={gamestate.activeStage} className="lg:col-span-1" />
|
||||
<OperatorProfile gamestate={newGamestate} user={mockDbUser} className="lg:col-span-2" />
|
||||
<TacticalMissions role={role} gamestate={newGamestate} className="lg:col-span-1" />
|
||||
|
||||
{/* Checkpoints & Stages */}
|
||||
<CheckpointsSystem stages={gamestate.stages} className="lg:col-span-3" />
|
||||
<CheckpointsSystem role={role} stages={oldGamestate.stages} className="lg:col-span-3" />
|
||||
|
||||
{/* Field Agent Only Features */}
|
||||
{role === 'FIELD_AGENT' && (
|
||||
<DetailedBadges badges={gamestate.badges} className="lg:col-span-3" />
|
||||
<DetailedBadges badges={oldGamestate.badges} className="lg:col-span-3" />
|
||||
)}
|
||||
|
||||
{/* Owner / Admin Tops */}
|
||||
@@ -497,7 +632,7 @@ export default function ProCanvas() {
|
||||
|
||||
{/* Shared Bottom Row */}
|
||||
<Leaderboard
|
||||
title={role === 'FIELD_AGENT' ? "Squad Rankings" : "Global Leaderboard"}
|
||||
title="Org Leaderboard"
|
||||
icon={role === 'OWNER' ? Star : Trophy}
|
||||
className="lg:col-span-2"
|
||||
/>
|
||||
@@ -518,3 +653,11 @@ export default function ProCanvas() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ProCanvas() {
|
||||
return (
|
||||
<GamificationProvider>
|
||||
<ProCanvasContent />
|
||||
</GamificationProvider>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user