migrant-web/resources/js/Layouts/EmployerLayout.jsx
2026-06-19 16:05:22 +05:30

357 lines
21 KiB
JavaScript

import React, { useEffect } from 'react';
import { Link, usePage } from '@inertiajs/react';
import { toast } from 'sonner';
import {
LayoutDashboard,
Search,
Bookmark,
MessageSquare,
CreditCard,
User,
LogOut,
Bell,
CheckCircle,
AlertCircle,
ArrowRight,
UserCheck,
Megaphone,
Briefcase,
Heart,
FileText,
ChevronDown,
LifeBuoy
} from 'lucide-react';
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator
} from '@/components/ui/dropdown-menu';
import { useTranslation, languages } from '../lib/LanguageContext';
export default function EmployerLayout({ children, title, fullPage = false }) {
const { url, props } = usePage();
const { auth, unread_messages_count, flash } = props;
const { locale, setLocale, t } = useTranslation();
useEffect(() => {
if (flash?.success) {
toast.success(flash.success);
}
if (flash?.error) {
toast.error(flash.error);
}
if (flash?.first_login) {
// Elegant first login celebratory alert
toast.success(`🎉 ${t('welcome_title', 'Welcome to Marketplace!')}`, {
description: t('welcome_desc', 'Your employer profile is verified and active with 30 days of Premium Access.'),
duration: 6000,
});
}
}, [flash, t]);
const user = auth?.user || {
name: 'Guest',
company_name: '',
email: '',
subscription_status: 'active',
subscription_expires_at: '',
};
const isSubActive = true;
const navItems = [
{ name: 'Dashboard', translationKey: 'dashboard', href: '/employer/dashboard', icon: LayoutDashboard },
{ name: 'Find Workers', translationKey: 'find_workers', href: '/employer/workers', icon: Search },
{ name: 'Shortlist', translationKey: 'shortlist', href: '/employer/shortlist', icon: Bookmark },
{ name: 'Hired Workers', translationKey: 'candidates', href: '/employer/candidates', icon: UserCheck },
{ name: 'Messages', translationKey: 'messages', href: '/employer/messages', icon: MessageSquare, badge: unread_messages_count },
{ name: 'Charity Events', translationKey: 'charity_events', href: '/employer/events', icon: Heart },
{ name: 'Subscription', translationKey: 'subscription', href: '/employer/subscription', icon: CreditCard },
{ name: 'Payment History', translationKey: 'payment_history', href: '/employer/payments', icon: FileText },
{ name: 'My Profile', translationKey: 'my_profile', href: '/employer/profile', icon: User },
{ name: 'Help & Support', translationKey: 'help_support', href: '/employer/support', icon: LifeBuoy },
];
const mobileTabs = [
{ name: 'Search', translationKey: 'search', href: '/employer/workers', icon: Search },
{ name: 'Shortlist', translationKey: 'shortlist', href: '/employer/shortlist', icon: Bookmark },
{ name: 'Hired Workers', translationKey: 'candidates', href: '/employer/candidates', icon: UserCheck },
{ name: 'Messages', translationKey: 'messages', href: '/employer/messages', icon: MessageSquare, badge: unread_messages_count },
{ name: 'Profile', translationKey: 'profile', href: '/employer/profile', icon: User },
];
return (
<div className="min-h-screen flex flex-col bg-slate-50 font-sans select-none">
{/* Top Subscription Gate Banner */}
{!isSubActive && !fullPage && (
<div className="bg-[#185FA5] text-white px-4 py-2.5 text-xs sm:text-sm font-medium flex items-center justify-between shadow-sm z-50">
<div className="flex items-center space-x-2 truncate">
<AlertCircle className="w-4 h-4 text-amber-300 flex-shrink-0 animate-bounce" />
<span className="truncate">{t('sub_warning', 'You need an active subscription to contact workers.')}</span>
</div>
<Link
href="/employer/subscription"
className="bg-white text-[#185FA5] hover:bg-slate-100 px-3 py-1 rounded-lg text-xs font-bold transition-colors inline-flex items-center space-x-1 flex-shrink-0 shadow-sm"
>
<span>{t('subscribe_now', 'Subscribe now')}</span>
<ArrowRight className="w-3 h-3" />
</Link>
</div>
)}
{/* Mobile Header Navbar */}
{!fullPage && (
<header className="lg:hidden sticky top-0 z-40 bg-white border-b border-slate-200 px-4 h-14 flex items-center justify-between">
<div className="flex items-center space-x-2.5">
<div className="w-8 h-8 bg-[#185FA5] text-white rounded-lg flex items-center justify-center font-bold text-base shadow-sm">
M
</div>
<span className="font-bold text-lg text-slate-800 tracking-tight truncate">{t('employer_portal', 'Employer Portal')}</span>
</div>
<div className="flex items-center space-x-3">
<Link href="/employer/messages" className="relative p-2 text-slate-600 hover:bg-slate-100 rounded-lg">
<Bell className="w-5 h-5" />
{unread_messages_count > 0 && (
<span className="absolute top-1.5 right-1.5 w-2 h-2 bg-red-500 rounded-full animate-ping" />
)}
</Link>
<div className="w-8 h-8 rounded-full bg-slate-100 border border-slate-200 flex items-center justify-center font-bold text-xs text-[#185FA5]">
{user.name.charAt(0)}
</div>
</div>
</header>
)}
<div className="flex flex-1 flex-col lg:flex-row h-screen overflow-hidden">
{/* Desktop Sidebar (lg+) */}
<aside className="hidden lg:flex w-64 bg-white border-r border-slate-200 flex-col justify-between flex-shrink-0 sticky top-0 h-screen">
<div>
{/* Top Section */}
<div className="p-6 border-b border-slate-100 space-y-4">
<div className="flex items-center space-x-3">
<div className="w-10 h-10 bg-[#185FA5] text-white rounded-xl flex items-center justify-center font-bold text-xl shadow-md">
M
</div>
<span className="font-bold text-xl text-slate-800 tracking-tight">{t('employer_portal', 'Employer Portal')}</span>
</div>
{/* Subscription Status Pill */}
<Link href="/employer/subscription" className="block">
{isSubActive ? (
<div className="flex items-center justify-between px-3 py-1.5 bg-emerald-50 border border-emerald-200 rounded-lg text-xs font-semibold text-emerald-700 hover:bg-emerald-100 transition-colors">
<div className="flex items-center space-x-1.5 truncate">
<CheckCircle className="w-3.5 h-3.5 text-emerald-600 flex-shrink-0" />
<span className="truncate">{t('active_until', 'Active until')} {user.subscription_expires_at || 'Dec 31'}</span>
</div>
</div>
) : (
<div className="flex items-center justify-between px-3 py-1.5 bg-red-50 border border-red-200 rounded-lg text-xs font-semibold text-red-700 hover:bg-red-100 transition-colors">
<div className="flex items-center space-x-1.5 truncate">
<AlertCircle className="w-3.5 h-3.5 text-red-600 flex-shrink-0" />
<span className="truncate">{t('expired_renew', 'Expired — Renew now')}</span>
</div>
</div>
)}
</Link>
</div>
{/* Navigation Items */}
<nav className="p-4 space-y-1">
{navItems.map((item) => {
const Icon = item.icon;
let isActive = url.startsWith(item.href);
if (url.startsWith('/employer/workers/')) {
const isHired = props.worker?.status === 'Hired' || props.worker?.availability_status === 'Hired';
if (isHired) {
if (item.href === '/employer/candidates') {
isActive = true;
} else if (item.href === '/employer/workers') {
isActive = false;
}
}
}
return (
<Link
key={item.name}
href={item.href}
className={`flex items-center justify-between px-4 py-3 rounded-xl text-sm transition-all duration-200 group ${
isActive
? 'bg-[#185FA5] text-white font-bold shadow-lg shadow-blue-900/20 scale-[1.02]'
: 'text-slate-600 hover:bg-slate-50 hover:text-slate-900'
}`}
>
<div className="flex items-center space-x-3">
<Icon className={`w-5 h-5 transition-colors ${isActive ? 'text-white' : 'text-slate-400 group-hover:text-slate-900'}`} />
<span className="tracking-tight">{t(item.translationKey, item.name)}</span>
</div>
{item.badge > 0 && (
<span className={`text-[10px] font-black px-2 py-0.5 rounded-full ${isActive ? 'bg-white text-[#185FA5]' : 'bg-[#185FA5] text-white'}`}>
{item.badge}
</span>
)}
</Link>
);
})}
</nav>
</div>
</aside>
{/* Main Content Area */}
<main className="flex-1 flex flex-col min-w-0 h-screen relative overflow-hidden bg-[#FAFBFF]">
{/* Top Desktop Header */}
{!fullPage && (
<header className="hidden lg:flex h-16 bg-white border-b border-slate-200 items-center justify-between px-8 shrink-0 sticky top-0 z-30 shadow-sm">
<div className="flex items-center space-x-4">
<div className="text-xs font-bold text-slate-400 uppercase tracking-widest">
{title ? t(title.toLowerCase().replace(/\s+/g, '_'), title) : t('overview', 'Overview')}
</div>
</div>
<div className="flex items-center space-x-6">
{/* Language Switcher Dropdown */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="flex items-center space-x-2 px-3 py-1.5 bg-slate-50 hover:bg-slate-100 border border-slate-200 rounded-xl transition-all cursor-pointer outline-none select-none">
<span className="text-xs font-bold text-slate-700 flex items-center gap-1.5">
<span>{languages.find(l => l.code === locale)?.flag}</span>
<span className="uppercase text-[10px] font-black tracking-wider">{locale}</span>
</span>
<ChevronDown className="w-3.5 h-3.5 text-slate-500 transition-transform duration-200" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48 mt-2 p-1 rounded-xl border border-slate-200 bg-white/95 backdrop-blur-md shadow-xl z-50">
<DropdownMenuLabel className="text-[9px] font-black text-slate-400 uppercase tracking-widest px-2.5 py-2">Select Language</DropdownMenuLabel>
<DropdownMenuSeparator className="bg-slate-100" />
{languages.map((lang) => (
<DropdownMenuItem
key={lang.code}
onClick={() => setLocale(lang.code)}
className={`flex items-center justify-between px-2.5 py-2 text-xs font-bold rounded-lg cursor-pointer transition-all ${
locale === lang.code
? 'bg-[#185FA5]/10 text-[#185FA5] font-black'
: 'text-slate-600 hover:bg-slate-50 hover:text-slate-900'
}`}
>
<div className="flex items-center space-x-2">
<span className="text-base">{lang.flag}</span>
<span>{lang.nativeName}</span>
</div>
{locale === lang.code && (
<div className="w-1.5 h-1.5 rounded-full bg-[#185FA5]" />
)}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
{/* Profile Section with Dropdown */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="flex items-center space-x-3 pl-6 border-l border-slate-200 group outline-none">
<div className="text-right hidden sm:block">
<div className="text-xs font-black text-slate-900 group-hover:text-[#185FA5] transition-colors">{user.name}</div>
<div className="text-[10px] font-bold text-slate-400 uppercase tracking-tighter">{t('employer_account', 'Employer Account')}</div>
</div>
<div className="w-10 h-10 rounded-xl bg-blue-100 text-[#185FA5] flex items-center justify-center font-black text-xs border border-blue-200 shadow-sm transition-transform group-hover:scale-105">
{user.name.charAt(0)}
</div>
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56 mt-2">
<DropdownMenuLabel className="text-[10px] font-black text-slate-400 uppercase tracking-widest px-2 py-1.5">{t('my_account', 'My Account')}</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem asChild>
<Link href="/employer/profile" className="flex items-center space-x-2 text-xs font-bold text-slate-600 cursor-pointer">
<User className="w-4 h-4" />
<span>{t('profile_settings', 'Profile Settings')}</span>
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link href="/employer/subscription" className="flex items-center space-x-2 text-xs font-bold text-slate-600 cursor-pointer">
<CreditCard className="w-4 h-4" />
<span>{t('subscription', 'Subscription')}</span>
</Link>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem asChild>
<Link
href={route('employer.logout')}
method="post"
as="button"
className="w-full flex items-center space-x-2 text-xs font-bold text-rose-600 cursor-pointer"
>
<LogOut className="w-4 h-4" />
<span>{t('sign_out', 'Sign Out')}</span>
</Link>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</header>
)}
<div className={`flex-1 overflow-y-auto ${fullPage ? 'p-0' : 'p-4 sm:p-8 pb-24 lg:pb-8'}`}>
{title && !fullPage && (
<h1 className="text-2xl font-black text-slate-900 mb-6 tracking-tight lg:hidden">
{title ? t(title.toLowerCase().replace(/\s+/g, '_'), title) : ''}
</h1>
)}
{children}
</div>
</main>
</div>
{/* Mobile Bottom Tab Bar */}
{!fullPage && (
<nav className="lg:hidden fixed bottom-0 left-0 right-0 bg-white border-t border-slate-200 h-16 px-4 flex items-center justify-around z-40">
{mobileTabs.map((tab) => {
const Icon = tab.icon;
let isActive = url.startsWith(tab.href);
if (url.startsWith('/employer/workers/')) {
const isHired = props.worker?.status === 'Hired' || props.worker?.availability_status === 'Hired';
if (isHired) {
if (tab.href === '/employer/candidates') {
isActive = true;
} else if (tab.href === '/employer/workers') {
isActive = false;
}
}
}
return (
<Link
key={tab.name}
href={tab.href}
className={`flex flex-col items-center justify-center space-y-1 w-16 h-full ${
isActive ? 'text-[#185FA5]' : 'text-slate-400 hover:text-slate-600'
}`}
>
<div className="relative">
<Icon className="w-5 h-5" />
{tab.badge > 0 && (
<span className="absolute -top-1 -right-2 w-3.5 h-3.5 bg-red-500 text-white rounded-full flex items-center justify-center text-[8px] font-bold">
{tab.badge}
</span>
)}
</div>
<span className="text-[10px] font-bold">{t(tab.translationKey, tab.name)}</span>
</Link>
);
})}
</nav>
)}
</div>
);
}