updates
This commit is contained in:
@@ -23,7 +23,9 @@ import {
|
||||
Upload,
|
||||
Loader2,
|
||||
Check,
|
||||
XCircle
|
||||
XCircle,
|
||||
Award,
|
||||
Shield
|
||||
} from 'lucide-react';
|
||||
import { pageContentService, PageContent, PageType, UpdatePageContentData, bannerService, Banner } from '../../services/api';
|
||||
import { toast } from 'react-toastify';
|
||||
@@ -138,7 +140,6 @@ const PageContentDashboard: React.FC = () => {
|
||||
subtitle: contents.contact.subtitle || '',
|
||||
description: contents.contact.description || '',
|
||||
content: contents.contact.content || '',
|
||||
contact_info: contents.contact.contact_info || { phone: '', email: '', address: '' },
|
||||
map_url: contents.contact.map_url || '',
|
||||
meta_title: contents.contact.meta_title || '',
|
||||
meta_description: contents.contact.meta_description || '',
|
||||
@@ -165,9 +166,9 @@ const PageContentDashboard: React.FC = () => {
|
||||
setFooterData({
|
||||
title: contents.footer.title || '',
|
||||
description: contents.footer.description || '',
|
||||
contact_info: contents.footer.contact_info || { phone: '', email: '', address: '' },
|
||||
social_links: contents.footer.social_links || {},
|
||||
footer_links: contents.footer.footer_links || { quick_links: [], support_links: [] },
|
||||
badges: contents.footer.badges || [],
|
||||
meta_title: contents.footer.meta_title || '',
|
||||
meta_description: contents.footer.meta_description || '',
|
||||
});
|
||||
@@ -190,7 +191,13 @@ const PageContentDashboard: React.FC = () => {
|
||||
const handleSave = async (pageType: PageType, data: UpdatePageContentData) => {
|
||||
try {
|
||||
setSaving(true);
|
||||
await pageContentService.updatePageContent(pageType, data);
|
||||
// Remove contact_info for contact and footer pages since it's now managed centrally
|
||||
const { contact_info, ...dataToSave } = data;
|
||||
if (pageType === 'contact' || pageType === 'footer') {
|
||||
await pageContentService.updatePageContent(pageType, dataToSave);
|
||||
} else {
|
||||
await pageContentService.updatePageContent(pageType, data);
|
||||
}
|
||||
toast.success(`${pageType.charAt(0).toUpperCase() + pageType.slice(1)} content saved successfully`);
|
||||
await fetchAllPageContents();
|
||||
} catch (error: any) {
|
||||
@@ -993,44 +1000,11 @@ const PageContentDashboard: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<div className="border-t border-gray-200 pt-6">
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-4">Contact Information</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Phone</label>
|
||||
<input
|
||||
type="tel"
|
||||
value={contactData.contact_info?.phone || ''}
|
||||
onChange={(e) => setContactData({
|
||||
...contactData,
|
||||
contact_info: { ...contactData.contact_info, phone: e.target.value }
|
||||
})}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
value={contactData.contact_info?.email || ''}
|
||||
onChange={(e) => setContactData({
|
||||
...contactData,
|
||||
contact_info: { ...contactData.contact_info, email: e.target.value }
|
||||
})}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Address</label>
|
||||
<input
|
||||
type="text"
|
||||
value={contactData.contact_info?.address || ''}
|
||||
onChange={(e) => setContactData({
|
||||
...contactData,
|
||||
contact_info: { ...contactData.contact_info, address: e.target.value }
|
||||
})}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
/>
|
||||
</div>
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4">
|
||||
<p className="text-sm text-blue-800">
|
||||
<strong>Note:</strong> Contact information (phone, email, address) is now managed centrally in <strong>Settings → Company Info</strong>.
|
||||
These fields will be displayed across the entire application.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1065,6 +1039,23 @@ const PageContentDashboard: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-gray-200 pt-6">
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-4">Help Message</h3>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Additional Information Text</label>
|
||||
<textarea
|
||||
value={contactData.content || ''}
|
||||
onChange={(e) => setContactData({ ...contactData, content: e.target.value })}
|
||||
rows={3}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
placeholder="Our team is here to help you with any questions about your stay, bookings, or special requests. We're committed to exceeding your expectations."
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-2">
|
||||
This text will appear below the contact information and map on the contact page.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Meta Title</label>
|
||||
@@ -1213,44 +1204,11 @@ const PageContentDashboard: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<div className="border-t border-gray-200 pt-6">
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-4">Contact Information</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Phone</label>
|
||||
<input
|
||||
type="tel"
|
||||
value={footerData.contact_info?.phone || ''}
|
||||
onChange={(e) => setFooterData({
|
||||
...footerData,
|
||||
contact_info: { ...footerData.contact_info, phone: e.target.value }
|
||||
})}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
value={footerData.contact_info?.email || ''}
|
||||
onChange={(e) => setFooterData({
|
||||
...footerData,
|
||||
contact_info: { ...footerData.contact_info, email: e.target.value }
|
||||
})}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Address</label>
|
||||
<input
|
||||
type="text"
|
||||
value={footerData.contact_info?.address || ''}
|
||||
onChange={(e) => setFooterData({
|
||||
...footerData,
|
||||
contact_info: { ...footerData.contact_info, address: e.target.value }
|
||||
})}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
/>
|
||||
</div>
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4">
|
||||
<p className="text-sm text-blue-800">
|
||||
<strong>Note:</strong> Contact information (phone, email, address) is now managed centrally in <strong>Settings → Company Info</strong>.
|
||||
These fields will be displayed across the entire application, including the footer.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1325,6 +1283,123 @@ const PageContentDashboard: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-gray-200 pt-6">
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-4">Footer Badges</h3>
|
||||
<p className="text-sm text-gray-600 mb-4">Customize the badges displayed in the footer (e.g., "5-Star Rated", "Award Winning").</p>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Badge 1 */}
|
||||
<div className="space-y-4 p-6 bg-gray-50 rounded-xl border border-gray-200">
|
||||
<label className="flex items-center gap-2 text-sm font-bold text-gray-900 tracking-wide">
|
||||
<Award className="w-4 h-4 text-gray-600" />
|
||||
Badge 1
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={footerData.badges?.[0]?.text || ''}
|
||||
onChange={(e) => {
|
||||
const badges = footerData.badges || [];
|
||||
const updated = [...badges];
|
||||
if (updated[0]) {
|
||||
updated[0] = { ...updated[0], text: e.target.value };
|
||||
} else {
|
||||
updated[0] = { text: e.target.value, icon: 'Award' };
|
||||
}
|
||||
setFooterData({ ...footerData, badges: updated });
|
||||
}}
|
||||
placeholder="5-Star Rated"
|
||||
className="w-full px-4 py-3 bg-white border border-gray-300 rounded-xl shadow-sm focus:ring-2 focus:ring-purple-500/50 focus:border-purple-500 transition-all duration-200 text-sm"
|
||||
/>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-gray-700 mb-2">Icon</label>
|
||||
<select
|
||||
value={footerData.badges?.[0]?.icon || 'Award'}
|
||||
onChange={(e) => {
|
||||
const badges = footerData.badges || [];
|
||||
const updated = [...badges];
|
||||
if (updated[0]) {
|
||||
updated[0] = { ...updated[0], icon: e.target.value };
|
||||
} else {
|
||||
updated[0] = { text: '', icon: e.target.value };
|
||||
}
|
||||
setFooterData({ ...footerData, badges: updated });
|
||||
}}
|
||||
className="w-full px-4 py-3 bg-white border border-gray-300 rounded-xl shadow-sm focus:ring-2 focus:ring-purple-500/50 focus:border-purple-500 transition-all duration-200 text-sm"
|
||||
>
|
||||
<option value="Award">Award</option>
|
||||
<option value="Star">Star</option>
|
||||
<option value="Trophy">Trophy</option>
|
||||
<option value="Medal">Medal</option>
|
||||
<option value="BadgeCheck">Badge Check</option>
|
||||
<option value="CheckCircle">Check Circle</option>
|
||||
<option value="Shield">Shield</option>
|
||||
<option value="Heart">Heart</option>
|
||||
<option value="Crown">Crown</option>
|
||||
<option value="Gem">Gem</option>
|
||||
<option value="Zap">Zap</option>
|
||||
<option value="Target">Target</option>
|
||||
<option value="TrendingUp">Trending Up</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Badge 2 */}
|
||||
<div className="space-y-4 p-6 bg-gray-50 rounded-xl border border-gray-200">
|
||||
<label className="flex items-center gap-2 text-sm font-bold text-gray-900 tracking-wide">
|
||||
<Shield className="w-4 h-4 text-gray-600" />
|
||||
Badge 2
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={footerData.badges?.[1]?.text || ''}
|
||||
onChange={(e) => {
|
||||
const badges = footerData.badges || [];
|
||||
const updated = [...badges];
|
||||
if (updated[1]) {
|
||||
updated[1] = { ...updated[1], text: e.target.value };
|
||||
} else {
|
||||
updated[1] = { text: e.target.value, icon: 'Shield' };
|
||||
}
|
||||
setFooterData({ ...footerData, badges: updated });
|
||||
}}
|
||||
placeholder="Award Winning"
|
||||
className="w-full px-4 py-3 bg-white border border-gray-300 rounded-xl shadow-sm focus:ring-2 focus:ring-purple-500/50 focus:border-purple-500 transition-all duration-200 text-sm"
|
||||
/>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-gray-700 mb-2">Icon</label>
|
||||
<select
|
||||
value={footerData.badges?.[1]?.icon || 'Shield'}
|
||||
onChange={(e) => {
|
||||
const badges = footerData.badges || [];
|
||||
const updated = [...badges];
|
||||
if (updated[1]) {
|
||||
updated[1] = { ...updated[1], icon: e.target.value };
|
||||
} else {
|
||||
updated[1] = { text: '', icon: e.target.value };
|
||||
}
|
||||
setFooterData({ ...footerData, badges: updated });
|
||||
}}
|
||||
className="w-full px-4 py-3 bg-white border border-gray-300 rounded-xl shadow-sm focus:ring-2 focus:ring-purple-500/50 focus:border-purple-500 transition-all duration-200 text-sm"
|
||||
>
|
||||
<option value="Award">Award</option>
|
||||
<option value="Star">Star</option>
|
||||
<option value="Trophy">Trophy</option>
|
||||
<option value="Medal">Medal</option>
|
||||
<option value="BadgeCheck">Badge Check</option>
|
||||
<option value="CheckCircle">Check Circle</option>
|
||||
<option value="Shield">Shield</option>
|
||||
<option value="Heart">Heart</option>
|
||||
<option value="Crown">Crown</option>
|
||||
<option value="Gem">Gem</option>
|
||||
<option value="Zap">Zap</option>
|
||||
<option value="Target">Target</option>
|
||||
<option value="TrendingUp">Trending Up</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end pt-4 border-t border-gray-200">
|
||||
<button
|
||||
onClick={() => handleSave('footer', footerData)}
|
||||
|
||||
Reference in New Issue
Block a user