updates
This commit is contained in:
@@ -607,42 +607,6 @@ const PageContentDashboard: React.FC = () => {
|
||||
{/* Home Tab */}
|
||||
{activeTab === 'home' && (
|
||||
<div className="space-y-8">
|
||||
{/* Seed Data Button */}
|
||||
<div className="bg-gradient-to-r from-purple-50 to-indigo-50 border-2 border-purple-200 rounded-xl p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-1">Quick Start</h3>
|
||||
<p className="text-sm text-gray-600">Load pre-configured luxury content to get started quickly</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (window.confirm('This will add pre-configured luxury content to your home page. Continue?')) {
|
||||
// Merge seed data with existing data, preserving all existing fields
|
||||
const seedData = luxuryContentSeed.home;
|
||||
setHomeData((prevData) => ({
|
||||
...prevData,
|
||||
// Update luxury-related fields from seed data
|
||||
luxury_section_title: seedData.luxury_section_title,
|
||||
luxury_section_subtitle: seedData.luxury_section_subtitle,
|
||||
luxury_section_image: seedData.luxury_section_image || prevData.luxury_section_image || '',
|
||||
luxury_features: Array.isArray(seedData.luxury_features) ? [...seedData.luxury_features] : [],
|
||||
luxury_gallery: Array.isArray(seedData.luxury_gallery) ? [...seedData.luxury_gallery] : (prevData.luxury_gallery || []),
|
||||
luxury_testimonials: Array.isArray(seedData.luxury_testimonials) ? [...seedData.luxury_testimonials] : [],
|
||||
about_preview_title: seedData.about_preview_title || prevData.about_preview_title || '',
|
||||
about_preview_content: seedData.about_preview_content || prevData.about_preview_content || '',
|
||||
about_preview_image: seedData.about_preview_image || prevData.about_preview_image || '',
|
||||
}));
|
||||
toast.success('Luxury content loaded successfully! Review and save when ready.');
|
||||
}
|
||||
}}
|
||||
className="px-6 py-2 bg-gradient-to-r from-purple-500 to-indigo-500 text-white rounded-lg font-semibold hover:from-purple-600 hover:to-indigo-600 transition-all shadow-lg"
|
||||
>
|
||||
Load Seed Data
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Home Page Content Section */}
|
||||
<div className="bg-white/90 backdrop-blur-xl rounded-2xl shadow-xl border border-gray-200/50 p-8">
|
||||
<h2 className="text-3xl font-extrabold text-gray-900 mb-6">Home Page Content</h2>
|
||||
|
||||
@@ -18,7 +18,9 @@ import {
|
||||
Mail,
|
||||
Building2,
|
||||
Upload,
|
||||
Image as ImageIcon
|
||||
Image as ImageIcon,
|
||||
MessageCircle,
|
||||
Clock
|
||||
} from 'lucide-react';
|
||||
import { toast } from 'react-toastify';
|
||||
import adminPrivacyService, {
|
||||
@@ -107,6 +109,8 @@ const SettingsPage: React.FC = () => {
|
||||
company_email: '',
|
||||
company_address: '',
|
||||
tax_rate: 0,
|
||||
chat_working_hours_start: 9,
|
||||
chat_working_hours_end: 17,
|
||||
});
|
||||
const [logoPreview, setLogoPreview] = useState<string | null>(null);
|
||||
const [faviconPreview, setFaviconPreview] = useState<string | null>(null);
|
||||
@@ -234,6 +238,8 @@ const SettingsPage: React.FC = () => {
|
||||
company_email: companyRes.data.company_email || '',
|
||||
company_address: companyRes.data.company_address || '',
|
||||
tax_rate: companyRes.data.tax_rate || 0,
|
||||
chat_working_hours_start: companyRes.data.chat_working_hours_start || 9,
|
||||
chat_working_hours_end: companyRes.data.chat_working_hours_end || 17,
|
||||
});
|
||||
|
||||
|
||||
@@ -2220,6 +2226,57 @@ const SettingsPage: React.FC = () => {
|
||||
Default tax rate percentage to be applied to all invoices (e.g., 10 for 10%). This will be used for all bookings unless overridden.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{}
|
||||
<div className="border-t border-gray-200 pt-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
|
||||
<MessageCircle className="w-5 h-5 text-amber-600" />
|
||||
Chat Working Hours
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="space-y-4">
|
||||
<label className="flex items-center gap-2 text-sm font-bold text-gray-900 tracking-wide">
|
||||
<Clock className="w-4 h-4 text-gray-600" />
|
||||
Start Hour (24-hour format)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
max="23"
|
||||
value={companyFormData.chat_working_hours_start || 9}
|
||||
onChange={(e) =>
|
||||
setCompanyFormData({ ...companyFormData, chat_working_hours_start: parseInt(e.target.value) || 9 })
|
||||
}
|
||||
placeholder="9"
|
||||
className="w-full px-4 py-3.5 bg-white border border-gray-300 rounded-xl shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-sm"
|
||||
/>
|
||||
<p className="text-xs text-gray-500">
|
||||
Hour when chat support becomes available (0-23, e.g., 9 for 9 AM)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<label className="flex items-center gap-2 text-sm font-bold text-gray-900 tracking-wide">
|
||||
<Clock className="w-4 h-4 text-gray-600" />
|
||||
End Hour (24-hour format)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
max="23"
|
||||
value={companyFormData.chat_working_hours_end || 17}
|
||||
onChange={(e) =>
|
||||
setCompanyFormData({ ...companyFormData, chat_working_hours_end: parseInt(e.target.value) || 17 })
|
||||
}
|
||||
placeholder="17"
|
||||
className="w-full px-4 py-3.5 bg-white border border-gray-300 rounded-xl shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-sm"
|
||||
/>
|
||||
<p className="text-xs text-gray-500">
|
||||
Hour when chat support ends (0-23, e.g., 17 for 5 PM)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { MessageCircle, CheckCircle, XCircle, Send, Clock, User, X } from 'lucide-react';
|
||||
import { MessageCircle, CheckCircle, XCircle, Send, Clock, User, X, RefreshCw } from 'lucide-react';
|
||||
import { chatService, type Chat, type ChatMessage } from '../../services/api';
|
||||
import { toast } from 'react-toastify';
|
||||
import { Loading, EmptyState } from '../../components/common';
|
||||
@@ -311,22 +311,22 @@ const ChatManagementPage: React.FC = () => {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
return (
|
||||
<span className="px-2 py-1 text-xs font-semibold rounded-full bg-amber-100 text-amber-800 border border-amber-200">
|
||||
<Clock className="w-3 h-3 inline mr-1" />
|
||||
<span className="px-3 py-1.5 text-xs font-semibold rounded-full bg-gradient-to-r from-amber-50 to-yellow-50 text-amber-800 border border-amber-200/60 shadow-sm">
|
||||
<Clock className="w-3 h-3 inline mr-1.5" />
|
||||
Pending
|
||||
</span>
|
||||
);
|
||||
case 'active':
|
||||
return (
|
||||
<span className="px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800 border border-green-200">
|
||||
<CheckCircle className="w-3 h-3 inline mr-1" />
|
||||
<span className="px-3 py-1.5 text-xs font-semibold rounded-full bg-gradient-to-r from-emerald-50 to-green-50 text-emerald-800 border border-emerald-200/60 shadow-sm">
|
||||
<CheckCircle className="w-3 h-3 inline mr-1.5" />
|
||||
Active
|
||||
</span>
|
||||
);
|
||||
case 'closed':
|
||||
return (
|
||||
<span className="px-2 py-1 text-xs font-semibold rounded-full bg-gray-100 text-gray-800 border border-gray-200">
|
||||
<XCircle className="w-3 h-3 inline mr-1" />
|
||||
<span className="px-3 py-1.5 text-xs font-semibold rounded-full bg-gradient-to-r from-slate-50 to-gray-50 text-slate-700 border border-slate-200/60 shadow-sm">
|
||||
<XCircle className="w-3 h-3 inline mr-1.5" />
|
||||
Closed
|
||||
</span>
|
||||
);
|
||||
@@ -343,22 +343,29 @@ const ChatManagementPage: React.FC = () => {
|
||||
<div className="space-y-6 bg-gradient-to-br from-slate-50 via-white to-slate-50 min-h-screen -m-6 p-8">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-slate-900">Chat Management</h1>
|
||||
<p className="text-slate-600 mt-2">Manage customer support chats</p>
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="h-1 w-16 bg-gradient-to-r from-[#d4af37] to-[#c9a227] rounded-full"></div>
|
||||
<h1 className="text-3xl font-serif font-bold bg-gradient-to-r from-slate-900 via-slate-800 to-slate-900 bg-clip-text text-transparent tracking-tight">
|
||||
Chat Management
|
||||
</h1>
|
||||
</div>
|
||||
<p className="text-slate-600 mt-2 font-light">Manage customer support chats with elegance</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={fetchChats}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
className="px-6 py-2.5 bg-gradient-to-r from-[#d4af37] to-[#c9a227] text-slate-900 rounded-lg font-semibold hover:from-[#c9a227] hover:to-[#d4af37] transition-all duration-300 shadow-lg shadow-[#d4af37]/30 hover:shadow-xl hover:shadow-[#d4af37]/40 flex items-center gap-2"
|
||||
>
|
||||
<RefreshCw className="w-4 h-4" />
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 h-[calc(100vh-200px)]">
|
||||
{}
|
||||
<div className="bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden flex flex-col">
|
||||
<div className="p-4 border-b border-gray-200 bg-gradient-to-r from-blue-50 to-blue-100">
|
||||
<h2 className="font-semibold text-slate-900">Chats ({chats.length})</h2>
|
||||
<div className="luxury-glass rounded-xl shadow-2xl border border-[#d4af37]/20 overflow-hidden flex flex-col">
|
||||
<div className="p-4 border-b border-[#d4af37]/20 bg-gradient-to-r from-[#d4af37]/10 via-[#c9a227]/10 to-[#d4af37]/10 relative">
|
||||
<div className="absolute top-0 left-0 right-0 h-0.5 bg-gradient-to-r from-transparent via-[#d4af37] to-transparent"></div>
|
||||
<h2 className="font-serif font-semibold text-slate-900 tracking-wide">Chats ({chats.length})</h2>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{chats.length === 0 ? (
|
||||
@@ -372,8 +379,10 @@ const ChatManagementPage: React.FC = () => {
|
||||
<div
|
||||
key={chat.id}
|
||||
onClick={() => setSelectedChat(chat)}
|
||||
className={`p-4 cursor-pointer hover:bg-gray-50 transition-colors ${
|
||||
selectedChat?.id === chat.id ? 'bg-blue-50 border-l-4 border-blue-600' : ''
|
||||
className={`p-4 cursor-pointer hover:bg-gradient-to-r hover:from-[#d4af37]/5 hover:to-[#c9a227]/5 transition-all duration-300 ${
|
||||
selectedChat?.id === chat.id
|
||||
? 'bg-gradient-to-r from-[#d4af37]/10 to-[#c9a227]/10 border-l-4 border-[#d4af37] shadow-sm'
|
||||
: 'border-l-4 border-transparent'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
@@ -397,7 +406,7 @@ const ChatManagementPage: React.FC = () => {
|
||||
e.stopPropagation();
|
||||
handleAcceptChat(chat.id);
|
||||
}}
|
||||
className="mt-2 w-full px-3 py-1.5 bg-green-600 text-white text-sm rounded-lg hover:bg-green-700 transition-colors"
|
||||
className="mt-2 w-full px-3 py-1.5 bg-gradient-to-r from-[#d4af37] to-[#c9a227] text-slate-900 text-sm font-semibold rounded-lg hover:from-[#c9a227] hover:to-[#d4af37] transition-all duration-300 shadow-md shadow-[#d4af37]/30 hover:shadow-lg"
|
||||
>
|
||||
Accept Chat
|
||||
</button>
|
||||
@@ -410,19 +419,20 @@ const ChatManagementPage: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{}
|
||||
<div className="lg:col-span-2 bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden flex flex-col">
|
||||
<div className="lg:col-span-2 luxury-glass rounded-xl shadow-2xl border border-[#d4af37]/20 overflow-hidden flex flex-col">
|
||||
{selectedChat ? (
|
||||
<>
|
||||
<div className="p-4 border-b border-gray-200 bg-gradient-to-r from-blue-50 to-blue-100 flex items-center justify-between">
|
||||
<div className="p-4 border-b border-[#d4af37]/20 bg-gradient-to-r from-[#d4af37]/10 via-[#c9a227]/10 to-[#d4af37]/10 flex items-center justify-between relative">
|
||||
<div className="absolute top-0 left-0 right-0 h-0.5 bg-gradient-to-r from-transparent via-[#d4af37] to-transparent"></div>
|
||||
<div>
|
||||
<h2 className="font-semibold text-slate-900">
|
||||
<h2 className="font-serif font-semibold text-slate-900 tracking-wide">
|
||||
{selectedChat.visitor_name || 'Guest'}
|
||||
</h2>
|
||||
{selectedChat.visitor_email && (
|
||||
<p className="text-sm text-gray-600">{selectedChat.visitor_email}</p>
|
||||
<p className="text-sm text-slate-600 font-light">{selectedChat.visitor_email}</p>
|
||||
)}
|
||||
{selectedChat.staff_name && (
|
||||
<p className="text-xs text-blue-600 mt-1">
|
||||
<p className="text-xs text-[#d4af37] mt-1 font-medium">
|
||||
Accepted by: {selectedChat.staff_name}
|
||||
</p>
|
||||
)}
|
||||
@@ -441,7 +451,7 @@ const ChatManagementPage: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto p-4 space-y-4 bg-gray-50">
|
||||
<div className="flex-1 overflow-y-auto p-4 space-y-4 bg-gradient-to-b from-slate-50/50 to-white">
|
||||
{loadingMessages ? (
|
||||
<Loading text="Loading messages..." />
|
||||
) : messages.length === 0 ? (
|
||||
@@ -458,25 +468,25 @@ const ChatManagementPage: React.FC = () => {
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`max-w-[80%] rounded-lg p-3 ${
|
||||
className={`max-w-[80%] rounded-xl p-4 shadow-lg ${
|
||||
message.sender_type === 'staff'
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'bg-white text-gray-800 border border-gray-200'
|
||||
? 'bg-gradient-to-br from-[#d4af37] to-[#c9a227] text-slate-900 border border-[#d4af37]/30'
|
||||
: 'bg-white text-slate-800 border border-slate-200/60 shadow-sm'
|
||||
}`}
|
||||
>
|
||||
{message.sender_type === 'visitor' && (
|
||||
<div className="text-xs font-semibold mb-1 text-blue-600">
|
||||
<div className="text-xs font-semibold mb-1.5 text-[#d4af37] tracking-wide">
|
||||
{message.sender_name || 'Visitor'}
|
||||
</div>
|
||||
)}
|
||||
<p className="text-sm whitespace-pre-wrap break-words">
|
||||
<p className="text-sm whitespace-pre-wrap break-words leading-relaxed">
|
||||
{message.message}
|
||||
</p>
|
||||
<p
|
||||
className={`text-xs mt-1 ${
|
||||
className={`text-xs mt-2 ${
|
||||
message.sender_type === 'staff'
|
||||
? 'text-blue-100'
|
||||
: 'text-gray-500'
|
||||
? 'text-slate-700/70'
|
||||
: 'text-slate-500'
|
||||
}`}
|
||||
>
|
||||
{new Date(message.created_at).toLocaleTimeString([], {
|
||||
@@ -492,27 +502,27 @@ const ChatManagementPage: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{selectedChat.status !== 'closed' && (
|
||||
<div className="p-4 border-t border-gray-200 bg-white">
|
||||
<div className="flex gap-2">
|
||||
<div className="p-4 border-t border-[#d4af37]/20 bg-white/90 backdrop-blur-sm">
|
||||
<div className="flex gap-3">
|
||||
<input
|
||||
type="text"
|
||||
value={newMessage}
|
||||
onChange={(e) => setNewMessage(e.target.value)}
|
||||
onKeyPress={handleKeyPress}
|
||||
placeholder="Type your message..."
|
||||
className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
className="flex-1 px-4 py-3 border-2 border-slate-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-[#d4af37]/30 focus:border-[#d4af37] transition-all duration-200 font-light"
|
||||
disabled={selectedChat.status === 'pending'}
|
||||
/>
|
||||
<button
|
||||
onClick={handleSendMessage}
|
||||
disabled={!newMessage.trim() || selectedChat.status === 'pending'}
|
||||
className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors flex items-center gap-2"
|
||||
className="bg-gradient-to-r from-[#d4af37] to-[#c9a227] text-slate-900 px-5 py-3 rounded-xl hover:from-[#c9a227] hover:to-[#d4af37] disabled:bg-gray-300 disabled:text-gray-500 disabled:cursor-not-allowed transition-all duration-300 flex items-center gap-2 font-semibold shadow-lg shadow-[#d4af37]/30 hover:shadow-xl disabled:shadow-none"
|
||||
>
|
||||
<Send className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
{selectedChat.status === 'pending' && (
|
||||
<p className="text-xs text-gray-500 mt-2">
|
||||
<p className="text-xs text-slate-500 mt-2 font-light">
|
||||
Accept the chat to start messaging
|
||||
</p>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user