350 lines
21 KiB
JavaScript
350 lines
21 KiB
JavaScript
import React, { useState, useEffect, useRef } from 'react';
|
|
import { Head, Link } from '@inertiajs/react';
|
|
import EmployerLayout from '../../../Layouts/EmployerLayout';
|
|
import {
|
|
Send,
|
|
ArrowLeft,
|
|
CheckCircle2,
|
|
Globe2,
|
|
DollarSign,
|
|
Sparkles,
|
|
MoreVertical,
|
|
Phone,
|
|
Video,
|
|
Paperclip,
|
|
Search,
|
|
Info,
|
|
Calendar,
|
|
Briefcase,
|
|
ShieldCheck,
|
|
ChevronRight,
|
|
MapPin,
|
|
Smile
|
|
} from 'lucide-react';
|
|
|
|
export default function Show({ conversation, initialMessages, conversations = [] }) {
|
|
const [messages, setMessages] = useState(initialMessages || []);
|
|
const [input, setInput] = useState('');
|
|
const [isTyping, setIsTyping] = useState(false);
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
const [showProfile, setShowProfile] = useState(false);
|
|
const messagesEndRef = useRef(null);
|
|
|
|
const scrollToBottom = () => {
|
|
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
};
|
|
|
|
useEffect(() => {
|
|
scrollToBottom();
|
|
}, [messages, isTyping]);
|
|
|
|
const filteredConversations = (conversations || []).filter(c =>
|
|
c.worker_name.toLowerCase().includes(searchTerm.toLowerCase())
|
|
);
|
|
|
|
const sendMsg = (text) => {
|
|
if (!text.trim()) return;
|
|
|
|
const newMsg = {
|
|
id: Date.now(),
|
|
sender: 'employer',
|
|
text: text,
|
|
time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
|
|
};
|
|
|
|
setMessages(prev => [...prev, newMsg]);
|
|
setInput('');
|
|
setIsTyping(true);
|
|
|
|
setTimeout(() => {
|
|
setIsTyping(false);
|
|
setMessages(prev => [...prev, {
|
|
id: Date.now() + 1,
|
|
sender: 'worker',
|
|
text: "Thank you for the message ma'am! I confirm I have received your request and look forward to speaking with you.",
|
|
time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
|
|
}]);
|
|
}, 1500 + Math.random() * 1000);
|
|
};
|
|
|
|
const handleFormSubmit = (e) => {
|
|
e.preventDefault();
|
|
sendMsg(input);
|
|
};
|
|
|
|
const chips = [
|
|
"Are you available for a video interview today?",
|
|
"What is your final expected salary?",
|
|
"Can you transfer your visa immediately?"
|
|
];
|
|
|
|
return (
|
|
<EmployerLayout title={null} fullPage={true}>
|
|
<Head title={`Chat with ${conversation.worker_name} - Messages`} />
|
|
|
|
<div className="flex h-full bg-[#FAFBFF] overflow-hidden select-none">
|
|
{/* Conversation List Sidebar (Desktop) */}
|
|
<aside className="hidden xl:flex w-96 border-r border-slate-200 flex-col flex-shrink-0 bg-white shadow-sm z-30">
|
|
<div className="p-6 border-b border-slate-100">
|
|
<div className="flex items-center justify-between mb-6">
|
|
<h2 className="font-black text-2xl text-slate-900 tracking-tight">Messages</h2>
|
|
<button className="p-2 bg-slate-50 text-slate-400 rounded-xl hover:text-[#185FA5] transition-colors">
|
|
<Search className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
<div className="relative">
|
|
<input
|
|
type="text"
|
|
placeholder="Search by name..."
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
className="w-full pl-5 pr-4 py-3.5 bg-slate-50 border-none rounded-2xl text-sm font-bold focus:ring-2 focus:ring-blue-100 transition-all placeholder:text-slate-400"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex-1 overflow-y-auto divide-y divide-slate-50">
|
|
{filteredConversations.map((c) => (
|
|
<Link
|
|
key={c.id}
|
|
href={`/employer/messages/${c.id}`}
|
|
className={`flex items-center space-x-4 p-5 transition-all relative group ${
|
|
c.id === conversation.id ? 'bg-blue-50/50' : 'hover:bg-slate-50'
|
|
}`}
|
|
>
|
|
{c.id === conversation.id && (
|
|
<div className="absolute left-0 top-0 bottom-0 w-1.5 bg-[#185FA5] rounded-r-full shadow-[0_0_15px_rgba(24,95,165,0.4)]" />
|
|
)}
|
|
|
|
<div className="relative flex-shrink-0">
|
|
<div className={`w-14 h-14 rounded-[20px] text-white flex items-center justify-center font-black text-xl shadow-lg ${
|
|
c.id === conversation.id ? 'bg-gradient-to-br from-[#185FA5] to-blue-600' : 'bg-slate-200 text-slate-400'
|
|
}`}>
|
|
{c.worker_name.charAt(0)}
|
|
</div>
|
|
{c.online && (
|
|
<span className="absolute -bottom-1 -right-1 w-4 h-4 bg-emerald-500 border-4 border-white rounded-full shadow-sm" />
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-center justify-between mb-1">
|
|
<span className={`text-sm font-black truncate ${c.id === conversation.id ? 'text-[#185FA5]' : 'text-slate-900'}`}>
|
|
{c.worker_name}
|
|
</span>
|
|
<span className="text-[10px] text-slate-400 font-bold uppercase tracking-tighter">{c.sent_at}</span>
|
|
</div>
|
|
<p className={`text-xs font-bold truncate leading-tight ${c.unread ? 'text-slate-900' : 'text-slate-400'}`}>
|
|
{c.last_message}
|
|
</p>
|
|
</div>
|
|
|
|
{c.unread && (
|
|
<div className="w-2.5 h-2.5 bg-red-500 rounded-full flex-shrink-0 shadow-[0_0_8px_rgba(239,68,68,0.4)]" />
|
|
)}
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</aside>
|
|
|
|
{/* Main Chat Interface */}
|
|
<div className="flex-1 flex flex-col h-full bg-white relative z-10 shadow-2xl">
|
|
{/* Glassmorphism Header */}
|
|
<div className="bg-white/80 backdrop-blur-xl p-4 sm:p-6 border-b border-slate-100 flex items-center justify-between z-40 sticky top-0">
|
|
<div className="flex items-center space-x-4">
|
|
<Link
|
|
href="/employer/messages"
|
|
className="lg:hidden p-2.5 text-slate-400 hover:text-[#185FA5] hover:bg-blue-50 rounded-2xl transition-all"
|
|
>
|
|
<ArrowLeft className="w-6 h-6" />
|
|
</Link>
|
|
|
|
<div className="relative group">
|
|
<div className="w-12 h-12 rounded-2xl bg-gradient-to-br from-[#185FA5] to-blue-600 text-white flex items-center justify-center font-black text-lg shadow-xl group-hover:scale-105 transition-transform">
|
|
{conversation.worker_name.charAt(0)}
|
|
</div>
|
|
{conversation.online && (
|
|
<span className="absolute -bottom-1 -right-1 w-4 h-4 bg-emerald-500 border-4 border-white rounded-full shadow-lg" />
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<div className="flex items-center gap-2">
|
|
<h3 className="font-black text-slate-900 text-lg tracking-tight">{conversation.worker_name}</h3>
|
|
<span className="bg-emerald-50 text-emerald-600 p-0.5 rounded-full shadow-sm">
|
|
<ShieldCheck className="w-4 h-4" />
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center space-x-2 mt-0.5">
|
|
<div className={`w-2 h-2 rounded-full ${conversation.online ? 'bg-emerald-500' : 'bg-slate-300'}`} />
|
|
<span className="text-[10px] font-black text-slate-400 uppercase tracking-widest">
|
|
{conversation.online ? 'Active Now' : 'Last seen 2h ago'}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center space-x-3">
|
|
<Link
|
|
href={`/employer/workers/${conversation.id}`}
|
|
className="p-3 bg-slate-50 text-slate-400 hover:text-[#185FA5] hover:bg-blue-50 rounded-2xl transition-all shadow-sm"
|
|
title="View Full Profile"
|
|
>
|
|
<Info className="w-5 h-5" />
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Chat Messages */}
|
|
<div className="flex-1 px-6 py-8 overflow-y-auto bg-[#F8FAFC]/50 space-y-8 scroll-smooth">
|
|
<div className="flex flex-col items-center mb-10">
|
|
<div className="bg-white border border-slate-200 px-6 py-2 rounded-full shadow-sm">
|
|
<span className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Today, Dec 15</span>
|
|
</div>
|
|
</div>
|
|
|
|
{messages.map((msg, idx) => {
|
|
const isEmp = msg.sender === 'employer';
|
|
return (
|
|
<div
|
|
key={msg.id}
|
|
className={`flex w-full ${isEmp ? 'justify-end' : 'justify-start'} animate-in fade-in slide-in-from-bottom-4 duration-500`}
|
|
>
|
|
<div className={`flex flex-col ${isEmp ? 'items-end' : 'items-start'} max-w-[80%] sm:max-w-[65%]`}>
|
|
<div className={`px-6 py-4 rounded-[32px] text-[13px] font-bold leading-relaxed shadow-xl relative ${
|
|
isEmp
|
|
? 'bg-[#185FA5] text-white rounded-br-lg shadow-blue-500/10'
|
|
: 'bg-white border border-slate-100 text-slate-800 rounded-bl-lg shadow-slate-200/40'
|
|
}`}>
|
|
{msg.text}
|
|
</div>
|
|
<div className="flex items-center space-x-2 mt-2 px-2">
|
|
<span className="text-[9px] font-black text-slate-400 uppercase tracking-tighter">{msg.time}</span>
|
|
{isEmp && <CheckCircle2 className="w-3 h-3 text-emerald-500" />}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
|
|
{isTyping && (
|
|
<div className="flex w-full justify-start animate-in fade-in slide-in-from-bottom-2">
|
|
<div className="bg-white border border-slate-100 px-6 py-4 rounded-[32px] rounded-bl-lg shadow-sm flex items-center space-x-1.5">
|
|
<div className="w-1.5 h-1.5 bg-[#185FA5] rounded-full animate-bounce [animation-delay:-0.3s]"></div>
|
|
<div className="w-1.5 h-1.5 bg-[#185FA5] rounded-full animate-bounce [animation-delay:-0.15s]"></div>
|
|
<div className="w-1.5 h-1.5 bg-[#185FA5] rounded-full animate-bounce"></div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
<div ref={messagesEndRef} />
|
|
</div>
|
|
|
|
{/* Quick Replies & Input */}
|
|
<div className="bg-white border-t border-slate-100 p-6 space-y-4 shadow-[0_-10px_40px_rgba(0,0,0,0.02)]">
|
|
<div className="flex items-center space-x-3 overflow-x-auto scrollbar-none pb-2">
|
|
<div className="flex-shrink-0 p-2 bg-indigo-50 text-indigo-500 rounded-xl">
|
|
<Sparkles className="w-4 h-4" />
|
|
</div>
|
|
{chips.map((chip, idx) => (
|
|
<button
|
|
key={idx}
|
|
onClick={() => sendMsg(chip)}
|
|
className="bg-slate-50 hover:bg-[#185FA5] text-slate-600 hover:text-white px-5 py-2.5 rounded-2xl text-[11px] font-black uppercase tracking-widest flex-shrink-0 transition-all border border-slate-100 hover:border-transparent hover:scale-105 active:scale-95"
|
|
>
|
|
{chip}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
<form onSubmit={handleFormSubmit} className="flex items-center gap-4">
|
|
<button type="button" className="p-4 bg-slate-50 text-slate-400 hover:text-slate-600 rounded-[24px] transition-all flex-shrink-0 border border-slate-100">
|
|
<Paperclip className="w-6 h-6" />
|
|
</button>
|
|
<div className="flex-1 relative group">
|
|
<input
|
|
value={input}
|
|
onChange={(e) => setInput(e.target.value)}
|
|
placeholder="Type a secure message..."
|
|
className="w-full pl-6 pr-14 py-5 bg-slate-50 border-none rounded-[32px] text-sm font-bold focus:ring-4 focus:ring-blue-100 transition-all outline-none group-focus-within:bg-white border-2 border-transparent group-focus-within:border-slate-100"
|
|
/>
|
|
<div className="absolute right-4 top-1/2 -translate-y-1/2 p-2 text-slate-300">
|
|
<Smile className="w-5 h-5" />
|
|
</div>
|
|
</div>
|
|
<button
|
|
type="submit"
|
|
disabled={!input.trim()}
|
|
className="bg-[#185FA5] hover:bg-[#144f8a] disabled:bg-slate-200 text-white p-5 rounded-[24px] transition-all shadow-xl shadow-blue-500/20 flex items-center justify-center flex-shrink-0 group hover:-translate-y-1 active:translate-y-0"
|
|
>
|
|
<Send className="w-6 h-6 group-hover:translate-x-1 group-hover:-translate-y-1 transition-transform" />
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Candidate Context Sidebar (Right) */}
|
|
{showProfile && (
|
|
<aside className="hidden lg:flex w-80 2xl:w-96 border-l border-slate-200 flex-col flex-shrink-0 bg-white z-20 animate-in slide-in-from-right-10 duration-500">
|
|
<div className="p-8 space-y-8 overflow-y-auto">
|
|
<div className="text-center space-y-4">
|
|
<div className="w-24 h-24 rounded-[40px] bg-gradient-to-br from-[#185FA5] to-blue-600 text-white flex items-center justify-center font-black text-3xl mx-auto shadow-2xl relative">
|
|
{conversation.worker_name.charAt(0)}
|
|
<span className="absolute -bottom-2 -right-2 bg-emerald-500 p-2 rounded-2xl border-4 border-white shadow-lg">
|
|
<ShieldCheck className="w-5 h-5 text-white" />
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<h4 className="font-black text-2xl text-slate-900 tracking-tight">{conversation.worker_name}</h4>
|
|
<p className="text-[10px] font-black text-slate-400 uppercase tracking-[0.2em] mt-1">{conversation.category}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="bg-slate-50 p-4 rounded-3xl text-center">
|
|
<DollarSign className="w-4 h-4 text-emerald-600 mx-auto mb-1.5" />
|
|
<div className="text-[9px] font-black text-slate-400 uppercase tracking-widest">Expected</div>
|
|
<div className="text-sm font-black text-slate-900 mt-1 truncate">{conversation.salary}</div>
|
|
</div>
|
|
<div className="bg-slate-50 p-4 rounded-3xl text-center">
|
|
<MapPin className="w-4 h-4 text-blue-600 mx-auto mb-1.5" />
|
|
<div className="text-[9px] font-black text-slate-400 uppercase tracking-widest">Region</div>
|
|
<div className="text-sm font-black text-slate-900 mt-1 truncate">{conversation.nationality}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
<h5 className="text-[10px] font-black text-slate-400 uppercase tracking-widest pl-2">Recruitment Context</h5>
|
|
<div className="space-y-3">
|
|
{[
|
|
{ label: 'Visa Status', value: 'Transferable', icon: Briefcase, color: 'text-blue-500' },
|
|
{ label: 'Medical Test', value: 'Cleared', icon: ShieldCheck, color: 'text-emerald-500' },
|
|
{ label: 'Last Active', value: 'Today, 10:15 AM', icon: Calendar, color: 'text-slate-500' }
|
|
].map((item, i) => (
|
|
<div key={i} className="flex items-center justify-between p-4 bg-slate-50/50 border border-slate-100 rounded-3xl">
|
|
<div className="flex items-center space-x-3">
|
|
<item.icon className={`w-4 h-4 ${item.color}`} />
|
|
<span className="text-[11px] font-black text-slate-500 uppercase tracking-tighter">{item.label}</span>
|
|
</div>
|
|
<span className="text-[11px] font-black text-slate-900">{item.value}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="pt-4">
|
|
<Link
|
|
href={`/employer/workers/${conversation.id}`}
|
|
className="w-full bg-slate-900 text-white py-4 rounded-3xl text-xs font-black uppercase tracking-widest flex items-center justify-center space-x-2 shadow-xl shadow-slate-900/10 hover:-translate-y-1 transition-all"
|
|
>
|
|
<span>Full Dossier</span>
|
|
<ChevronRight className="w-4 h-4" />
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
)}
|
|
</div>
|
|
</EmployerLayout>
|
|
);
|
|
}
|