feat: Multi-role platform expansion, mobile nav redesign, and UI polish

- Add Owner, Contractor, Vendor, Subcontractor dashboards and role-based routing
- Owner now has superuser access to all Admin pages (dashboard, schedule, leaderboard)
- Redesign landing page mobile menu as slide-in sidebar (replaces broken Framer Motion overlay)
- Add body scroll lock to app sidebar for mobile consistency
- Fix Team Schedule to match Admin Dashboard design language (ambient glows, gradient header, SpotlightCard, zinc palette)
- Fix login page tab overflow — use abbreviated labels and grid layout for 6 role tabs
- Fix Recharts ResponsiveContainer warnings — replace height="100%" with fixed pixel heights
- Fix lightbox image viewer — unified nav bar, touch swipe support, no more overlapping text
- Add AI Assistant page, People/Vendor/Document management for Owner role
- Expand mock data store with contractor, vendor, and subcontractor data
This commit is contained in:
Satyam
2026-02-17 01:19:41 +05:30
parent 35cbdeb33c
commit 2eaac6b84a
44 changed files with 6857 additions and 662 deletions
+141 -113
View File
@@ -2,14 +2,13 @@ import React, { useEffect, useRef, useState } from 'react';
import Chatbot from '../components/Chatbot';
import { useAuth } from '../context/AuthContext';
import { useTheme } from '../context/ThemeContext';
import { ArrowRight, CheckCircle2, Home, Star, ShieldCheck, Clock, Award, TrendingDown, Users, MapPin, Phone, Mail, Instagram, Twitter, Youtube, Sun, Moon, LogOut, LayoutDashboard, User, Menu, X, ChevronDown } from 'lucide-react';
import { ArrowRight, CheckCircle2, Home, Star, ShieldCheck, Clock, Award, TrendingDown, Users, MapPin, Phone, Mail, Instagram, Twitter, Youtube, Sun, Moon, LogOut, LayoutDashboard, User, Menu, X, ChevronDown, Zap, Briefcase, Map, Tag } from 'lucide-react';
import { Link } from 'react-router-dom';
import { SpotlightCard } from '../components/SpotlightCard';
import { ComparisonSlider } from '../components/ComparisonSlider';
import Services from '../components/Services';
import IntelligenceMap from '../components/IntelligenceMap';
import { motion, AnimatePresence } from 'framer-motion';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import Logo from '../assets/images/LynkedUp_Pro_F_logo_Y.png';
@@ -132,6 +131,20 @@ const Landing = () => {
}
};
// Helper to determine dashboard path based on role
const getDashboardPath = (role) => {
switch (role) {
case 'OWNER': return '/owner/snapshot';
case 'ADMIN': return '/admin/dashboard';
case 'CONTRACTOR': return '/contractor/dashboard';
case 'SUBCONTRACTOR': return '/subcontractor/dashboard';
case 'VENDOR': return '/vendor/dashboard';
case 'FIELD_AGENT': return '/emp/fa/dashboard';
case 'CUSTOMER': return '/portal/profile';
default: return '/login';
}
};
return (
<div className="min-h-screen bg-zinc-50 dark:bg-[#050505] text-zinc-900 dark:text-white transition-colors duration-300 overflow-x-hidden selection:bg-emerald-500/30">
@@ -207,7 +220,7 @@ const Landing = () => {
) : (
<div className="flex items-center gap-2">
<Link
to={user.role === 'CUSTOMER' ? '/portal/profile' : '/emp/fa/dashboard'}
to={getDashboardPath(user.role)}
className="px-5 py-2 rounded-lg text-sm font-bold bg-blue-500 text-white hover:bg-blue-600 transition-colors shadow-sm shadow-blue-500/25"
>
{user.role === 'CUSTOMER' ? 'My Profile' : 'Dashboard'}
@@ -226,16 +239,10 @@ const Landing = () => {
{/* Mobile Menu Toggle */}
<div className="flex md:hidden items-center gap-2">
<button
onClick={toggleTheme}
className="p-2 rounded-lg hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors text-zinc-500 dark:text-zinc-400"
aria-label="Toggle Theme"
>
{theme === 'dark' ? <Sun size={18} /> : <Moon size={18} />}
</button>
<button
className="p-2 text-zinc-600 dark:text-zinc-400"
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
className="p-2 rounded-lg hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors text-zinc-600 dark:text-zinc-400"
aria-label="Toggle menu"
aria-expanded={isMobileMenuOpen}
>
{isMobileMenuOpen ? <X size={24} /> : <Menu size={24} />}
</button>
@@ -243,109 +250,128 @@ const Landing = () => {
</div>
</nav>
{/* ── Mobile Full-Screen Overlay ── */}
<AnimatePresence>
{isMobileMenuOpen && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
className="fixed inset-0 z-50 bg-white dark:bg-zinc-950 flex flex-col"
{/* ── Mobile Backdrop ── */}
{isMobileMenuOpen && (
<div
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-[100] md:hidden"
onClick={() => setIsMobileMenuOpen(false)}
aria-hidden="true"
/>
)}
{/* ── Mobile Sidebar — slides from left, mirrors Layout.jsx pattern ── */}
<aside
className={`
fixed inset-y-0 left-0 z-[101] w-72 md:hidden
bg-white dark:bg-[#09090b] border-r border-zinc-200 dark:border-white/5
transition-transform duration-300 ease-in-out flex flex-col shadow-2xl
${isMobileMenuOpen ? 'translate-x-0' : '-translate-x-full'}
`}
>
{/* Header */}
<div className="h-16 flex items-center justify-between px-4 border-b border-zinc-100 dark:border-white/5 shrink-0">
<div className="flex items-center gap-2">
<img src={Logo} alt="LynkedUp Pro" className="h-10 w-auto object-contain" />
</div>
<button
onClick={() => setIsMobileMenuOpen(false)}
className="p-2 rounded-lg hover:bg-zinc-100 dark:hover:bg-zinc-800 text-zinc-500 dark:text-zinc-400 transition-colors"
aria-label="Close menu"
>
{/* Overlay Header */}
<div className="flex items-center justify-between px-6 h-16">
<img src={Logo} alt="LynkedUp Pro" className="h-12 w-auto object-contain" />
<button
<X size={20} />
</button>
</div>
{/* Nav Links */}
<nav className="flex-1 px-3 py-6 space-y-1 overflow-y-auto">
{[
{ label: 'How It Works', id: 'how-it-works', icon: Zap },
{ label: 'Services', id: 'services', icon: Briefcase },
{ label: 'Intelligence', id: 'intelligence', icon: Map },
].map((item) => (
<button
key={item.id}
onClick={() => scrollToSection(item.id)}
className="w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-semibold text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-white/5 hover:text-zinc-900 dark:hover:text-white transition-colors"
>
<item.icon size={18} className="shrink-0 text-zinc-400 dark:text-zinc-500" />
{item.label}
</button>
))}
<Link
to="/login"
onClick={() => setIsMobileMenuOpen(false)}
className="w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-semibold text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-white/5 hover:text-zinc-900 dark:hover:text-white transition-colors"
>
<Tag size={18} className="shrink-0 text-zinc-400 dark:text-zinc-500" />
Pricing
</Link>
{/* Sign Up / Login nav item — visible only when logged out */}
{!user && (
<>
<div className="my-3 border-t border-zinc-100 dark:border-white/5" />
<Link
to="/login"
onClick={() => setIsMobileMenuOpen(false)}
className="p-2 text-zinc-600 dark:text-zinc-400"
aria-label="Close menu"
className="w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-semibold text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-500/10 transition-colors"
>
<X size={24} />
</button>
</div>
<User size={18} className="shrink-0" />
Sign Up / Login
</Link>
</>
)}
</nav>
{/* Overlay Links */}
<motion.div
initial="closed"
animate="open"
variants={{ open: { transition: { staggerChildren: 0.06 } }, closed: {} }}
className="flex-1 flex flex-col items-center justify-center gap-2 px-6"
>
{[
{ label: 'How It Works', id: 'how-it-works' },
{ label: 'Services', id: 'services' },
{ label: 'Intelligence', id: 'intelligence' },
].map((item) => (
<motion.button
key={item.id}
variants={{ closed: { opacity: 0, y: 20 }, open: { opacity: 1, y: 0 } }}
transition={{ duration: 0.3 }}
onClick={() => scrollToSection(item.id)}
className="text-2xl font-bold text-zinc-900 dark:text-white py-3 hover:text-blue-500 transition-colors"
>
{item.label}
</motion.button>
))}
<motion.div
variants={{ closed: { opacity: 0, y: 20 }, open: { opacity: 1, y: 0 } }}
transition={{ duration: 0.3 }}
{/* Footer */}
<div className="p-4 border-t border-zinc-100 dark:border-white/5 space-y-3 shrink-0">
{/* Theme Toggle */}
<button
onClick={toggleTheme}
className="w-full flex items-center justify-between px-3 py-2.5 rounded-lg text-zinc-500 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-white/5 hover:text-amber-500 transition-colors"
>
<span className="text-xs font-medium">Dark Mode</span>
{theme === 'dark' ? <Moon size={16} /> : <Sun size={16} />}
</button>
{/* CTAs */}
{!user ? (
<>
<Link
to="/login"
onClick={() => setIsMobileMenuOpen(false)}
className="block w-full text-center py-3 rounded-xl text-sm font-bold bg-blue-500 text-white hover:bg-blue-600 transition-colors shadow-sm shadow-blue-500/25"
>
<Link
to="/login"
onClick={() => setIsMobileMenuOpen(false)}
className="text-2xl font-bold text-zinc-900 dark:text-white py-3 hover:text-blue-500 transition-colors"
>
Pricing
</Link>
</motion.div>
</motion.div>
{/* Overlay Bottom Actions */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3, duration: 0.3 }}
className="px-6 pb-10 space-y-3"
>
{!user ? (
<>
<Link
to="/login"
onClick={() => setIsMobileMenuOpen(false)}
className="block w-full text-center py-4 rounded-xl text-base font-bold bg-blue-500 text-white hover:bg-blue-600 transition-colors shadow-sm"
>
Get Started
</Link>
<Link
to="/login"
onClick={() => setIsMobileMenuOpen(false)}
className="block w-full text-center py-4 rounded-xl text-base font-bold border border-zinc-200 dark:border-white/10 text-zinc-700 dark:text-zinc-300 hover:bg-zinc-50 dark:hover:bg-white/5 transition-colors"
>
Sign In
</Link>
</>
) : (
<>
<Link
to={user.role === 'CUSTOMER' ? '/portal/profile' : '/emp/fa/dashboard'}
onClick={() => setIsMobileMenuOpen(false)}
className="block w-full text-center py-4 rounded-xl text-base font-bold bg-blue-500 text-white hover:bg-blue-600 transition-colors shadow-sm"
>
{user.role === 'CUSTOMER' ? 'My Profile' : 'Dashboard'}
</Link>
<button
onClick={() => { useAuth().logout(); setIsMobileMenuOpen(false); }}
className="block w-full text-center py-4 rounded-xl text-base font-bold border border-red-200 dark:border-red-500/20 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/10 transition-colors"
>
Sign Out
</button>
</>
)}
</motion.div>
</motion.div>
)}
</AnimatePresence>
Get Started
</Link>
<Link
to="/login"
onClick={() => setIsMobileMenuOpen(false)}
className="block w-full text-center py-3 rounded-xl text-sm font-bold border border-zinc-200 dark:border-white/10 text-zinc-700 dark:text-zinc-300 hover:bg-zinc-50 dark:hover:bg-white/5 transition-colors"
>
Sign In
</Link>
</>
) : (
<>
<Link
to={getDashboardPath(user.role)}
onClick={() => setIsMobileMenuOpen(false)}
className="block w-full text-center py-3 rounded-xl text-sm font-bold bg-blue-500 text-white hover:bg-blue-600 transition-colors shadow-sm shadow-blue-500/25"
>
{user.role === 'CUSTOMER' ? 'My Profile' : 'Dashboard'}
</Link>
<Link
to="/login"
onClick={() => setIsMobileMenuOpen(false)}
className="block w-full text-center py-3 rounded-xl text-sm font-bold border border-red-200 dark:border-red-500/20 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/10 transition-colors"
>
Sign Out
</Link>
</>
)}
</div>
</aside>
{/* ============================================ */}
{/* HERO SECTION — "The Overhead View" */}
@@ -409,13 +435,15 @@ const Landing = () => {
<ArrowRight size={18} className="ml-2 transition-transform group-hover:translate-x-0.5" />
</Link>
{/* B2B: Contractors */}
<Link
to="/login"
<a
href="https://www.youtube.com/watch?v=dQw4w9WgXcQ"
target="_blank"
rel="noopener noreferrer"
className="group px-8 py-4 rounded-full bg-zinc-100 dark:bg-white/[0.06] hover:bg-zinc-200 dark:hover:bg-white/[0.10] border border-zinc-300 dark:border-white/[0.10] hover:border-zinc-400 dark:hover:border-white/[0.15] text-zinc-700 dark:text-white/90 font-bold text-sm transition-all flex items-center hover:scale-[1.02]"
>
Start Free Trial
See The Process
<ArrowRight size={18} className="ml-2 transition-transform group-hover:translate-x-0.5" />
</Link>
</a>
</div>
{/* Discount banner — Veterans, Seniors, Active Duty */}