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:
+141
-113
@@ -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 */}
|
||||
|
||||
Reference in New Issue
Block a user