This commit is contained in:
Iliyan Angelov
2025-11-30 22:43:09 +02:00
parent 24b40450dd
commit 39fcfff811
1610 changed files with 5442 additions and 1383 deletions

View File

@@ -1,636 +0,0 @@
import React, { useState, useEffect } from 'react';
import {
Hotel,
Heart,
MapPin,
Phone,
Mail,
Linkedin,
Twitter
} from 'lucide-react';
import * as LucideIcons from 'lucide-react';
import { Link } from 'react-router-dom';
import { pageContentService } from '../services/api';
import type { PageContent } from '../services/api/pageContentService';
import { useCompanySettings } from '../contexts/CompanySettingsContext';
import { createSanitizedHtml } from '../utils/htmlSanitizer';
const AboutPage: React.FC = () => {
const { settings } = useCompanySettings();
const [pageContent, setPageContent] = useState<PageContent | null>(null);
useEffect(() => {
const fetchPageContent = async () => {
try {
const response = await pageContentService.getAboutContent();
if (response.status === 'success' && response.data?.page_content) {
setPageContent(response.data.page_content);
if (response.data.page_content.meta_title) {
document.title = response.data.page_content.meta_title;
}
if (response.data.page_content.meta_description) {
let metaDescription = document.querySelector('meta[name="description"]');
if (!metaDescription) {
metaDescription = document.createElement('meta');
metaDescription.setAttribute('name', 'description');
document.head.appendChild(metaDescription);
}
metaDescription.setAttribute('content', response.data.page_content.meta_description);
}
}
} catch (err: any) {
console.error('Error fetching page content:', err);
}
};
fetchPageContent();
}, []);
const displayPhone = settings.company_phone || '+1 (234) 567-890';
const displayEmail = settings.company_email || 'info@luxuryhotel.com';
const displayAddress = settings.company_address || '123 Luxury Street\nCity, State 12345\nCountry';
const defaultValues = [
{
icon: 'Heart',
title: 'Passion',
description: 'We are passionate about hospitality and dedicated to creating exceptional experiences for every guest.'
},
{
icon: 'Award',
title: 'Excellence',
description: 'We strive for excellence in every aspect of our service, from the smallest detail to the grandest gesture.'
},
{
icon: 'Shield',
title: 'Integrity',
description: 'We conduct our business with honesty, transparency, and respect for our guests and community.'
},
{
icon: 'Users',
title: 'Service',
description: 'Our guests are at the heart of everything we do. Your comfort and satisfaction are our top priorities.'
}
];
const defaultFeatures = [
{
icon: 'Star',
title: 'Premium Accommodations',
description: 'Luxuriously appointed rooms and suites designed for ultimate comfort and relaxation.'
},
{
icon: 'Clock',
title: '24/7 Service',
description: 'Round-the-clock concierge and room service to attend to your needs at any time.'
},
{
icon: 'Award',
title: 'Award-Winning',
description: 'Recognized for excellence in hospitality and guest satisfaction.'
}
];
const values = pageContent?.values && pageContent.values.length > 0
? pageContent.values.map((v: any) => ({
icon: v.icon || defaultValues.find(d => d.title === v.title)?.icon || 'Heart',
title: v.title,
description: v.description
}))
: defaultValues;
const features = pageContent?.features && pageContent.features.length > 0
? pageContent.features.map((f: any) => ({
icon: f.icon || defaultFeatures.find(d => d.title === f.title)?.icon || 'Star',
title: f.title,
description: f.description
}))
: defaultFeatures;
const team = pageContent?.team && typeof pageContent.team === 'string'
? JSON.parse(pageContent.team)
: (Array.isArray(pageContent?.team) ? pageContent.team : []);
const timeline = pageContent?.timeline && typeof pageContent.timeline === 'string'
? JSON.parse(pageContent.timeline)
: (Array.isArray(pageContent?.timeline) ? pageContent.timeline : []);
const achievements = pageContent?.achievements && typeof pageContent.achievements === 'string'
? JSON.parse(pageContent.achievements)
: (Array.isArray(pageContent?.achievements) ? pageContent.achievements : []);
const getIconComponent = (iconName?: string) => {
if (!iconName) return Heart;
const IconComponent = (LucideIcons as any)[iconName] || Heart;
return IconComponent;
};
return (
<div className="min-h-screen bg-gradient-to-b from-slate-50 via-white to-slate-50">
{}
<div className={`relative ${pageContent?.about_hero_image ? '' : 'bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900'} text-white py-20 md:py-24 ${pageContent?.about_hero_image ? 'h-[400px] md:h-[450px] lg:h-[500px]' : ''} overflow-hidden`}>
{pageContent?.about_hero_image && (
<div className="absolute inset-0">
<img
src={pageContent.about_hero_image.startsWith('http') ? pageContent.about_hero_image : `${import.meta.env.VITE_API_URL?.replace('/api', '') || 'http://localhost:8000'}/${pageContent.about_hero_image}`}
alt="About Hero"
className="w-full h-full object-cover scale-105 transition-transform duration-[20s] ease-out hover:scale-100"
/>
<div className="absolute inset-0 bg-gradient-to-b from-black/70 via-black/50 to-black/70"></div>
<div className="absolute inset-0 bg-gradient-to-r from-[#d4af37]/10 via-transparent to-[#d4af37]/10"></div>
</div>
)}
{!pageContent?.about_hero_image && (
<div className="absolute inset-0">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_50%,rgba(212,175,55,0.1),transparent_70%)]"></div>
<div className="absolute top-0 left-0 w-full h-full bg-[linear-gradient(45deg,transparent_30%,rgba(212,175,55,0.05)_50%,transparent_70%)]"></div>
</div>
)}
<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div className="max-w-4xl mx-auto text-center">
{!pageContent?.about_hero_image && (
<div className="flex justify-center mb-8">
<div className="relative">
<div className="absolute inset-0 bg-gradient-to-br from-[#d4af37] via-[#f5d76e] to-[#d4af37] rounded-full blur-3xl opacity-40 animate-pulse"></div>
<div className="relative bg-gradient-to-br from-[#d4af37] to-[#c9a227] p-6 rounded-2xl shadow-2xl shadow-[#d4af37]/30">
<Hotel className="w-12 h-12 md:w-16 md:h-16 text-white drop-shadow-lg" />
</div>
</div>
</div>
)}
<div className="mb-6">
<div className="inline-block mb-4">
<div className="h-px w-20 bg-gradient-to-r from-transparent via-[#d4af37] to-transparent mx-auto"></div>
</div>
</div>
<h1 className="text-5xl md:text-6xl lg:text-7xl font-serif font-light mb-6 tracking-[0.02em] leading-tight">
<span className="bg-gradient-to-b from-white via-[#f5d76e] to-[#d4af37] bg-clip-text text-transparent drop-shadow-2xl">
{pageContent?.title || 'About Luxury Hotel'}
</span>
</h1>
<p className="text-lg md:text-xl lg:text-2xl text-gray-200 font-light leading-relaxed max-w-2xl mx-auto tracking-wide">
{pageContent?.subtitle || pageContent?.description || 'Where Excellence Meets Unforgettable Experiences'}
</p>
<div className="mt-8">
<div className="inline-block">
<div className="h-px w-20 bg-gradient-to-r from-transparent via-[#d4af37] to-transparent mx-auto"></div>
</div>
</div>
</div>
</div>
</div>
{}
<section className="py-20 md:py-28 bg-white relative overflow-hidden">
<div className="absolute top-0 left-0 w-full h-px bg-gradient-to-r from-transparent via-[#d4af37]/30 to-transparent"></div>
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="max-w-5xl mx-auto">
<div className="text-center mb-16">
<div className="inline-block mb-4">
<span className="text-sm font-semibold text-[#d4af37] tracking-[0.2em] uppercase">Our Heritage</span>
</div>
<h2 className="text-4xl md:text-5xl lg:text-6xl font-serif font-light text-gray-900 mb-6 tracking-tight">
Our Story
</h2>
<div className="flex items-center justify-center gap-4 mb-6">
<div className="h-px w-12 bg-gradient-to-r from-transparent to-[#d4af37]"></div>
<div className="w-2 h-2 bg-[#d4af37] rounded-full"></div>
<div className="h-px w-12 bg-gradient-to-l from-transparent to-[#d4af37]"></div>
</div>
</div>
<div className="prose prose-lg md:prose-xl max-w-none text-gray-700 leading-relaxed space-y-8">
{pageContent?.story_content ? (
<div
className="text-lg md:text-xl leading-relaxed font-light tracking-wide"
dangerouslySetInnerHTML={createSanitizedHtml(pageContent.story_content.replace(/\n/g, '<br />'))}
/>
) : (
<>
<p className="text-lg md:text-xl leading-relaxed font-light tracking-wide first-letter:text-5xl first-letter:font-serif first-letter:text-[#d4af37] first-letter:float-left first-letter:mr-2 first-letter:leading-none">
Welcome to Luxury Hotel, where timeless elegance meets modern sophistication.
Since our founding, we have been dedicated to providing exceptional hospitality
and creating unforgettable memories for our guests.
</p>
<p className="text-lg md:text-xl leading-relaxed font-light tracking-wide">
Nestled in the heart of the city, our hotel combines classic architecture with
contemporary amenities, offering a perfect blend of comfort and luxury. Every
detail has been carefully curated to ensure your stay exceeds expectations.
</p>
<p className="text-lg md:text-xl leading-relaxed font-light tracking-wide">
Our commitment to excellence extends beyond our beautiful rooms and facilities.
We believe in creating meaningful connections with our guests, understanding
their needs, and delivering personalized service that makes each visit special.
</p>
</>
)}
</div>
</div>
</div>
<div className="absolute bottom-0 left-0 w-full h-px bg-gradient-to-r from-transparent via-[#d4af37]/30 to-transparent"></div>
</section>
{}
<section className="py-20 md:py-28 bg-gradient-to-b from-slate-50 via-white to-slate-50 relative">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_30%_50%,rgba(212,175,55,0.03),transparent_50%)]"></div>
<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<div className="inline-block mb-4">
<span className="text-sm font-semibold text-[#d4af37] tracking-[0.2em] uppercase">Core Principles</span>
</div>
<h2 className="text-4xl md:text-5xl lg:text-6xl font-serif font-light text-gray-900 mb-6 tracking-tight">
Our Values
</h2>
<div className="flex items-center justify-center gap-4 mb-6">
<div className="h-px w-12 bg-gradient-to-r from-transparent to-[#d4af37]"></div>
<div className="w-2 h-2 bg-[#d4af37] rounded-full"></div>
<div className="h-px w-12 bg-gradient-to-l from-transparent to-[#d4af37]"></div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-8">
{values.map((value, index) => (
<div
key={value.title}
className="group relative bg-white/80 backdrop-blur-sm p-8 rounded-2xl shadow-lg hover:shadow-2xl transition-all duration-500 border border-gray-100 hover:border-[#d4af37]/30 hover:-translate-y-2"
style={{ animationDelay: `${index * 0.1}s` }}
>
<div className="absolute top-0 right-0 w-32 h-32 bg-gradient-to-br from-[#d4af37]/5 to-transparent rounded-bl-full opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
<div className="relative">
<div className="w-16 h-16 bg-gradient-to-br from-[#d4af37] via-[#f5d76e] to-[#d4af37] rounded-xl flex items-center justify-center mb-6 shadow-lg shadow-[#d4af37]/20 group-hover:scale-110 group-hover:rotate-3 transition-transform duration-500">
{(() => {
const ValueIcon = getIconComponent(value.icon);
return <ValueIcon className="w-8 h-8 text-white drop-shadow-md" />;
})()}
</div>
<h3 className="text-xl md:text-2xl font-serif font-semibold text-gray-900 mb-3 group-hover:text-[#d4af37] transition-colors duration-300">
{value.title}
</h3>
<p className="text-gray-600 leading-relaxed font-light text-sm md:text-base">
{value.description}
</p>
</div>
</div>
))}
</div>
</div>
</div>
</section>
{}
<section className="py-20 md:py-28 bg-white relative overflow-hidden">
<div className="absolute inset-0 bg-[linear-gradient(135deg,transparent_0%,rgba(212,175,55,0.02)_50%,transparent_100%)]"></div>
<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<div className="inline-block mb-4">
<span className="text-sm font-semibold text-[#d4af37] tracking-[0.2em] uppercase">Excellence Defined</span>
</div>
<h2 className="text-4xl md:text-5xl lg:text-6xl font-serif font-light text-gray-900 mb-6 tracking-tight">
Why Choose Us
</h2>
<div className="flex items-center justify-center gap-4 mb-6">
<div className="h-px w-12 bg-gradient-to-r from-transparent to-[#d4af37]"></div>
<div className="w-2 h-2 bg-[#d4af37] rounded-full"></div>
<div className="h-px w-12 bg-gradient-to-l from-transparent to-[#d4af37]"></div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 lg:gap-12">
{features.map((feature, index) => {
const FeatureIcon = getIconComponent(feature.icon);
return (
<div
key={feature.title || index}
className="group text-center p-8 relative"
style={{ animationDelay: `${index * 0.1}s` }}
>
<div className="absolute inset-0 bg-gradient-to-br from-[#d4af37]/5 to-transparent rounded-3xl opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
<div className="relative">
<div className="w-20 h-20 bg-gradient-to-br from-[#d4af37] via-[#f5d76e] to-[#d4af37] rounded-2xl flex items-center justify-center mx-auto mb-6 shadow-xl shadow-[#d4af37]/30 group-hover:scale-110 group-hover:shadow-2xl group-hover:shadow-[#d4af37]/40 transition-all duration-500">
<FeatureIcon className="w-10 h-10 text-white drop-shadow-lg" />
</div>
<h3 className="text-xl md:text-2xl font-serif font-semibold text-gray-900 mb-4 group-hover:text-[#d4af37] transition-colors duration-300">
{feature.title}
</h3>
<p className="text-gray-600 leading-relaxed font-light text-sm md:text-base">
{feature.description}
</p>
</div>
</div>
);
})}
</div>
</div>
</div>
</section>
{}
{(pageContent?.mission || pageContent?.vision) && (
<section className="py-20 md:py-28 bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 relative overflow-hidden">
<div className="absolute inset-0">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_50%,rgba(212,175,55,0.1),transparent_70%)]"></div>
<div className="absolute top-0 left-0 w-full h-full bg-[linear-gradient(45deg,transparent_30%,rgba(212,175,55,0.05)_50%,transparent_70%)]"></div>
</div>
<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div className="max-w-7xl mx-auto grid grid-cols-1 md:grid-cols-2 gap-8 lg:gap-12">
{pageContent.mission && (
<div className="group relative bg-white/95 backdrop-blur-sm p-10 md:p-12 rounded-2xl shadow-2xl border border-[#d4af37]/20 hover:border-[#d4af37]/40 transition-all duration-500 hover:shadow-[#d4af37]/20 hover:-translate-y-1">
<div className="absolute top-0 right-0 w-40 h-40 bg-gradient-to-br from-[#d4af37]/10 to-transparent rounded-bl-full opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
<div className="relative">
<div className="flex items-center gap-3 mb-6">
<div className="w-1 h-12 bg-gradient-to-b from-[#d4af37] to-[#f5d76e] rounded-full"></div>
<h2 className="text-3xl md:text-4xl font-serif font-light text-gray-900">Our Mission</h2>
</div>
<p className="text-gray-700 leading-relaxed text-base md:text-lg font-light tracking-wide">{pageContent.mission}</p>
</div>
</div>
)}
{pageContent.vision && (
<div className="group relative bg-white/95 backdrop-blur-sm p-10 md:p-12 rounded-2xl shadow-2xl border border-[#d4af37]/20 hover:border-[#d4af37]/40 transition-all duration-500 hover:shadow-[#d4af37]/20 hover:-translate-y-1">
<div className="absolute top-0 right-0 w-40 h-40 bg-gradient-to-br from-[#d4af37]/10 to-transparent rounded-bl-full opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
<div className="relative">
<div className="flex items-center gap-3 mb-6">
<div className="w-1 h-12 bg-gradient-to-b from-[#d4af37] to-[#f5d76e] rounded-full"></div>
<h2 className="text-3xl md:text-4xl font-serif font-light text-gray-900">Our Vision</h2>
</div>
<p className="text-gray-700 leading-relaxed text-base md:text-lg font-light tracking-wide">{pageContent.vision}</p>
</div>
</div>
)}
</div>
</div>
</section>
)}
{}
{team && team.length > 0 && (
<section className="py-20 md:py-28 bg-gradient-to-b from-white via-slate-50 to-white relative">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<div className="inline-block mb-4">
<span className="text-sm font-semibold text-[#d4af37] tracking-[0.2em] uppercase">Meet The Experts</span>
</div>
<h2 className="text-4xl md:text-5xl lg:text-6xl font-serif font-light text-gray-900 mb-6 tracking-tight">
Our Team
</h2>
<div className="flex items-center justify-center gap-4 mb-6">
<div className="h-px w-12 bg-gradient-to-r from-transparent to-[#d4af37]"></div>
<div className="w-2 h-2 bg-[#d4af37] rounded-full"></div>
<div className="h-px w-12 bg-gradient-to-l from-transparent to-[#d4af37]"></div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 lg:gap-10">
{team.map((member: any, index: number) => (
<div
key={index}
className="group relative bg-white rounded-2xl shadow-xl overflow-hidden hover:shadow-2xl transition-all duration-500 border border-gray-100 hover:border-[#d4af37]/30 hover:-translate-y-2"
>
<div className="absolute inset-0 bg-gradient-to-br from-[#d4af37]/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500 z-10"></div>
{member.image && (
<div className="relative overflow-hidden h-72">
<img
src={member.image.startsWith('http') ? member.image : `${import.meta.env.VITE_API_URL?.replace('/api', '') || 'http://localhost:8000'}/${member.image}`}
alt={member.name}
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-700"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
</div>
)}
<div className="p-8 relative z-10">
<h3 className="text-2xl font-serif font-semibold text-gray-900 mb-2 group-hover:text-[#d4af37] transition-colors duration-300">{member.name}</h3>
<p className="text-[#d4af37] font-medium mb-4 text-sm tracking-wide uppercase">{member.role}</p>
{member.bio && <p className="text-gray-600 text-sm mb-6 leading-relaxed font-light">{member.bio}</p>}
{member.social_links && (
<div className="flex gap-4 pt-4 border-t border-gray-100">
{member.social_links.linkedin && (
<a
href={member.social_links.linkedin}
target="_blank"
rel="noopener noreferrer"
className="w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center text-gray-600 hover:bg-[#d4af37] hover:text-white transition-all duration-300 group-hover:scale-110"
>
<Linkedin className="w-5 h-5" />
</a>
)}
{member.social_links.twitter && (
<a
href={member.social_links.twitter}
target="_blank"
rel="noopener noreferrer"
className="w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center text-gray-600 hover:bg-[#d4af37] hover:text-white transition-all duration-300 group-hover:scale-110"
>
<Twitter className="w-5 h-5" />
</a>
)}
</div>
)}
</div>
</div>
))}
</div>
</div>
</div>
</section>
)}
{}
{timeline && timeline.length > 0 && (
<section className="py-20 md:py-28 bg-gradient-to-b from-slate-50 via-white to-slate-50 relative overflow-hidden">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_70%_50%,rgba(212,175,55,0.03),transparent_50%)]"></div>
<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div className="max-w-6xl mx-auto">
<div className="text-center mb-16">
<div className="inline-block mb-4">
<span className="text-sm font-semibold text-[#d4af37] tracking-[0.2em] uppercase">Our Journey</span>
</div>
<h2 className="text-4xl md:text-5xl lg:text-6xl font-serif font-light text-gray-900 mb-6 tracking-tight">
Our History
</h2>
<div className="flex items-center justify-center gap-4 mb-6">
<div className="h-px w-12 bg-gradient-to-r from-transparent to-[#d4af37]"></div>
<div className="w-2 h-2 bg-[#d4af37] rounded-full"></div>
<div className="h-px w-12 bg-gradient-to-l from-transparent to-[#d4af37]"></div>
</div>
</div>
<div className="relative">
<div className="absolute left-8 md:left-1/2 transform md:-translate-x-1/2 w-1 h-full bg-gradient-to-b from-[#d4af37] via-[#f5d76e] to-[#d4af37] shadow-lg"></div>
<div className="space-y-12 md:space-y-16">
{timeline.map((event: any, index: number) => (
<div key={index} className={`relative flex items-center ${index % 2 === 0 ? 'md:flex-row' : 'md:flex-row-reverse'}`}>
<div className="absolute left-6 md:left-1/2 transform md:-translate-x-1/2 w-6 h-6 bg-gradient-to-br from-[#d4af37] to-[#c9a227] rounded-full border-4 border-white shadow-xl z-10 group-hover:scale-125 transition-transform duration-300"></div>
<div className={`ml-20 md:ml-0 md:w-5/12 ${index % 2 === 0 ? 'md:mr-auto md:pr-8' : 'md:ml-auto md:pl-8'}`}>
<div className="group bg-white/90 backdrop-blur-sm p-8 rounded-2xl shadow-xl hover:shadow-2xl transition-all duration-500 border border-gray-100 hover:border-[#d4af37]/30">
<div className="flex items-center gap-3 mb-4">
<div className="text-[#d4af37] font-bold text-2xl md:text-3xl font-serif">{event.year}</div>
<div className="h-px flex-1 bg-gradient-to-r from-[#d4af37] to-transparent"></div>
</div>
<h3 className="text-2xl md:text-3xl font-serif font-semibold text-gray-900 mb-3 group-hover:text-[#d4af37] transition-colors duration-300">{event.title}</h3>
<p className="text-gray-600 leading-relaxed font-light mb-4">{event.description}</p>
{event.image && (
<div className="mt-6 overflow-hidden rounded-xl">
<img
src={event.image.startsWith('http') ? event.image : `${import.meta.env.VITE_API_URL?.replace('/api', '') || 'http://localhost:8000'}/${event.image}`}
alt={event.title}
className="w-full h-56 md:h-64 object-cover group-hover:scale-110 transition-transform duration-700"
/>
</div>
)}
</div>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</section>
)}
{}
{achievements && achievements.length > 0 && (
<section className="py-20 md:py-28 bg-white relative overflow-hidden">
<div className="absolute inset-0 bg-[linear-gradient(135deg,transparent_0%,rgba(212,175,55,0.02)_50%,transparent_100%)]"></div>
<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<div className="inline-block mb-4">
<span className="text-sm font-semibold text-[#d4af37] tracking-[0.2em] uppercase">Recognition</span>
</div>
<h2 className="text-4xl md:text-5xl lg:text-6xl font-serif font-light text-gray-900 mb-6 tracking-tight">
Achievements & Awards
</h2>
<div className="flex items-center justify-center gap-4 mb-6">
<div className="h-px w-12 bg-gradient-to-r from-transparent to-[#d4af37]"></div>
<div className="w-2 h-2 bg-[#d4af37] rounded-full"></div>
<div className="h-px w-12 bg-gradient-to-l from-transparent to-[#d4af37]"></div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 lg:gap-10">
{achievements.map((achievement: any, index: number) => {
const AchievementIcon = getIconComponent(achievement.icon);
return (
<div
key={index}
className="group relative bg-gradient-to-br from-white to-slate-50 p-8 rounded-2xl shadow-xl hover:shadow-2xl transition-all duration-500 border border-gray-100 hover:border-[#d4af37]/40 hover:-translate-y-2"
>
<div className="absolute top-0 right-0 w-32 h-32 bg-gradient-to-br from-[#d4af37]/10 to-transparent rounded-bl-full opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
<div className="relative">
<div className="flex items-center gap-4 mb-6">
<div className="w-16 h-16 bg-gradient-to-br from-[#d4af37] via-[#f5d76e] to-[#d4af37] rounded-xl flex items-center justify-center shadow-lg shadow-[#d4af37]/20 group-hover:scale-110 group-hover:rotate-3 transition-transform duration-500">
<AchievementIcon className="w-8 h-8 text-white drop-shadow-md" />
</div>
{achievement.year && (
<div className="text-[#d4af37] font-bold text-2xl font-serif">{achievement.year}</div>
)}
</div>
<h3 className="text-xl md:text-2xl font-serif font-semibold text-gray-900 mb-3 group-hover:text-[#d4af37] transition-colors duration-300">{achievement.title}</h3>
<p className="text-gray-600 text-sm md:text-base leading-relaxed font-light mb-4">{achievement.description}</p>
{achievement.image && (
<div className="mt-6 overflow-hidden rounded-xl">
<img
src={achievement.image.startsWith('http') ? achievement.image : `${import.meta.env.VITE_API_URL?.replace('/api', '') || 'http://localhost:8000'}/${achievement.image}`}
alt={achievement.title}
className="w-full h-40 object-cover group-hover:scale-110 transition-transform duration-700"
/>
</div>
)}
</div>
</div>
);
})}
</div>
</div>
</div>
</section>
)}
{}
<section className="py-20 md:py-28 bg-gradient-to-br from-slate-50 via-white to-slate-50 relative overflow-hidden">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_50%,rgba(212,175,55,0.03),transparent_70%)]"></div>
<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div className="max-w-6xl mx-auto">
<div className="text-center mb-16">
<div className="inline-block mb-4">
<span className="text-sm font-semibold text-[#d4af37] tracking-[0.2em] uppercase">Connect With Us</span>
</div>
<h2 className="text-4xl md:text-5xl lg:text-6xl font-serif font-light text-gray-900 mb-6 tracking-tight">
Get In Touch
</h2>
<div className="flex items-center justify-center gap-4 mb-6">
<div className="h-px w-12 bg-gradient-to-r from-transparent to-[#d4af37]"></div>
<div className="w-2 h-2 bg-[#d4af37] rounded-full"></div>
<div className="h-px w-12 bg-gradient-to-l from-transparent to-[#d4af37]"></div>
</div>
<p className="text-gray-600 mt-6 text-lg font-light max-w-2xl mx-auto">
We'd love to hear from you. Contact us for reservations or inquiries.
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 lg:gap-10 mb-16">
<div className="group text-center p-8 bg-white/80 backdrop-blur-sm rounded-2xl shadow-xl hover:shadow-2xl transition-all duration-500 border border-gray-100 hover:border-[#d4af37]/40 hover:-translate-y-2">
<div className="w-16 h-16 bg-gradient-to-br from-[#d4af37] via-[#f5d76e] to-[#d4af37] rounded-2xl flex items-center justify-center mx-auto mb-6 shadow-lg shadow-[#d4af37]/20 group-hover:scale-110 group-hover:rotate-3 transition-transform duration-500">
<MapPin className="w-8 h-8 text-white drop-shadow-md" />
</div>
<h3 className="text-xl font-serif font-semibold text-gray-900 mb-4 group-hover:text-[#d4af37] transition-colors duration-300">
Address
</h3>
<p className="text-gray-600 leading-relaxed font-light">
{displayAddress
.split('\n').map((line, i) => (
<React.Fragment key={i}>
{line}
{i < displayAddress.split('\n').length - 1 && <br />}
</React.Fragment>
))}
</p>
</div>
<div className="group text-center p-8 bg-white/80 backdrop-blur-sm rounded-2xl shadow-xl hover:shadow-2xl transition-all duration-500 border border-gray-100 hover:border-[#d4af37]/40 hover:-translate-y-2" style={{ animationDelay: '0.1s' }}>
<div className="w-16 h-16 bg-gradient-to-br from-[#d4af37] via-[#f5d76e] to-[#d4af37] rounded-2xl flex items-center justify-center mx-auto mb-6 shadow-lg shadow-[#d4af37]/20 group-hover:scale-110 group-hover:rotate-3 transition-transform duration-500">
<Phone className="w-8 h-8 text-white drop-shadow-md" />
</div>
<h3 className="text-xl font-serif font-semibold text-gray-900 mb-4 group-hover:text-[#d4af37] transition-colors duration-300">
Phone
</h3>
<p className="text-gray-600 font-light">
<a href={`tel:${displayPhone.replace(/\s+/g, '').replace(/[()]/g, '')}`} className="hover:text-[#d4af37] transition-colors duration-300">
{displayPhone}
</a>
</p>
</div>
<div className="group text-center p-8 bg-white/80 backdrop-blur-sm rounded-2xl shadow-xl hover:shadow-2xl transition-all duration-500 border border-gray-100 hover:border-[#d4af37]/40 hover:-translate-y-2" style={{ animationDelay: '0.2s' }}>
<div className="w-16 h-16 bg-gradient-to-br from-[#d4af37] via-[#f5d76e] to-[#d4af37] rounded-2xl flex items-center justify-center mx-auto mb-6 shadow-lg shadow-[#d4af37]/20 group-hover:scale-110 group-hover:rotate-3 transition-transform duration-500">
<Mail className="w-8 h-8 text-white drop-shadow-md" />
</div>
<h3 className="text-xl font-serif font-semibold text-gray-900 mb-4 group-hover:text-[#d4af37] transition-colors duration-300">
Email
</h3>
<p className="text-gray-600 font-light">
<a href={`mailto:${displayEmail}`} className="hover:text-[#d4af37] transition-colors duration-300">
{displayEmail}
</a>
</p>
</div>
</div>
<div className="text-center">
<Link
to="/rooms"
className="group inline-flex items-center space-x-3 px-10 py-4 bg-gradient-to-r from-[#d4af37] via-[#f5d76e] to-[#d4af37] text-white rounded-xl hover:shadow-2xl hover:shadow-[#d4af37]/40 transition-all duration-500 font-medium text-lg tracking-wide relative overflow-hidden"
>
<span className="relative z-10">Explore Our Rooms</span>
<Hotel className="w-5 h-5 relative z-10 group-hover:translate-x-1 transition-transform duration-300" />
<div className="absolute inset-0 bg-gradient-to-r from-[#f5d76e] to-[#d4af37] opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
</Link>
</div>
</div>
</div>
</section>
</div>
);
};
export default AboutPage;

View File

@@ -1,179 +0,0 @@
import React, { useState, useEffect } from 'react';
import { Accessibility, ArrowLeft } from 'lucide-react';
import { Link } from 'react-router-dom';
import { pageContentService } from '../services/api';
import type { PageContent } from '../services/api/pageContentService';
import { useCompanySettings } from '../contexts/CompanySettingsContext';
import Loading from '../components/common/Loading';
import { createSanitizedHtml } from '../utils/htmlSanitizer';
const AccessibilityPage: React.FC = () => {
const { settings } = useCompanySettings();
const [pageContent, setPageContent] = useState<PageContent | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchPageContent = async () => {
try {
setLoading(true);
const response = await pageContentService.getAccessibilityContent();
if (response.status === 'success' && response.data?.page_content) {
const content = response.data.page_content;
if (content.content) {
const tempDiv = document.createElement('div');
tempDiv.innerHTML = content.content;
const allElements = tempDiv.querySelectorAll('*');
allElements.forEach((el) => {
const htmlEl = el as HTMLElement;
const tagName = htmlEl.tagName.toLowerCase();
const currentColor = htmlEl.style.color;
if (!currentColor || currentColor === 'black' || currentColor === '#000' || currentColor === '#000000') {
if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName)) {
htmlEl.style.color = '#ffffff';
} else if (['strong', 'b'].includes(tagName)) {
htmlEl.style.color = '#d4af37';
} else if (tagName === 'a') {
htmlEl.style.color = '#d4af37';
} else {
htmlEl.style.color = '#d1d5db';
}
}
});
content.content = tempDiv.innerHTML;
}
setPageContent(content);
if (content.meta_title) {
document.title = content.meta_title;
}
if (content.meta_description) {
let metaDescription = document.querySelector('meta[name="description"]');
if (!metaDescription) {
metaDescription = document.createElement('meta');
metaDescription.setAttribute('name', 'description');
document.head.appendChild(metaDescription);
}
metaDescription.setAttribute('content', content.meta_description);
}
}
} catch (err: any) {
console.error('Error fetching page content:', err);
// If page is disabled (404), set pageContent to null to show disabled message
if (err.response?.status === 404) {
setPageContent(null);
}
} finally {
setLoading(false);
}
};
fetchPageContent();
}, []);
if (loading) {
return <Loading />;
}
if (!pageContent) {
return (
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-screen relative -mt-6 -mb-6 flex items-center justify-center"
style={{
marginLeft: 'calc(50% - 50vw)',
marginRight: 'calc(50% - 50vw)',
width: '100vw',
paddingTop: '2rem',
paddingBottom: '2rem',
zIndex: 1
}}
>
<div className="text-center">
<Accessibility className="w-16 h-16 text-[#d4af37]/50 mx-auto mb-4" />
<h1 className="text-2xl font-elegant font-bold text-white mb-2">Accessibility</h1>
<p className="text-gray-400">This page is currently unavailable.</p>
<Link
to="/"
className="inline-flex items-center gap-2 text-[#d4af37]/80 hover:text-[#d4af37] mt-6 transition-all duration-300"
>
<ArrowLeft className="w-4 h-4" />
<span>Back to Home</span>
</Link>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-screen relative -mt-6 -mb-6"
style={{
marginLeft: 'calc(50% - 50vw)',
marginRight: 'calc(50% - 50vw)',
width: '100vw',
paddingTop: '2rem',
paddingBottom: '2rem',
zIndex: 1
}}
>
<div className="w-full px-3 sm:px-4 md:px-6 lg:px-8 py-8 sm:py-12 max-w-5xl mx-auto">
<Link
to="/"
className="inline-flex items-center gap-2 text-[#d4af37]/80 hover:text-[#d4af37] mb-6 transition-all duration-300 group font-light tracking-wide text-xs sm:text-sm"
>
<ArrowLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" />
<span>Back to Home</span>
</Link>
<div className="mb-8 sm:mb-12 text-center">
<div className="inline-flex items-center justify-center w-16 h-16 sm:w-20 sm:h-20 mb-4 sm:mb-6 bg-gradient-to-br from-[#d4af37]/20 to-[#c9a227]/10 rounded-full border border-[#d4af37]/30">
<Accessibility className="w-8 h-8 sm:w-10 sm:h-10 text-[#d4af37]" />
</div>
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-elegant font-bold text-white mb-3 sm:mb-4 tracking-tight leading-tight bg-gradient-to-r from-white via-[#d4af37] to-white bg-clip-text text-transparent">
{pageContent.title || 'Accessibility'}
</h1>
{pageContent.subtitle && (
<p className="text-base sm:text-lg text-gray-300 font-light tracking-wide">
{pageContent.subtitle}
</p>
)}
</div>
<div className="bg-gradient-to-br from-[#1a1a1a] to-[#0a0a0a] rounded-xl border border-[#d4af37]/20 backdrop-blur-xl shadow-2xl shadow-[#d4af37]/5 p-6 sm:p-8 lg:p-12">
<div
className="prose prose-invert prose-lg max-w-none text-gray-300
prose-headings:text-white prose-headings:font-elegant prose-headings:font-semibold
prose-h2:text-2xl prose-h2:mt-8 prose-h2:mb-4 prose-h2:border-b prose-h2:border-[#d4af37]/20 prose-h2:pb-2
prose-p:text-gray-300 prose-p:font-light prose-p:leading-relaxed prose-p:mb-4
prose-ul:text-gray-300 prose-ul:font-light prose-ul:my-4
prose-li:text-gray-300 prose-li:mb-2 prose-li:ml-4
prose-strong:text-[#d4af37] prose-strong:font-medium
prose-a:text-[#d4af37] prose-a:no-underline hover:prose-a:underline
[&_*]:text-gray-300 [&_h1]:text-white [&_h2]:text-white [&_h3]:text-white [&_h4]:text-white [&_h5]:text-white [&_h6]:text-white
[&_strong]:text-[#d4af37] [&_b]:text-[#d4af37] [&_a]:text-[#d4af37]"
style={{ color: '#d1d5db' }}
dangerouslySetInnerHTML={createSanitizedHtml(
pageContent.content || pageContent.description || '<p style="color: #d1d5db;">No content available.</p>'
)}
/>
</div>
{settings.company_email && (
<div className="mt-8 text-center">
<p className="text-sm text-gray-400 font-light">
For accessibility inquiries, contact us at{' '}
<a href={`mailto:${settings.company_email}`} className="text-[#d4af37] hover:underline">
{settings.company_email}
</a>
</p>
</div>
)}
</div>
</div>
);
};
export default AccessibilityPage;

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { Outlet } from 'react-router-dom';
import { SidebarAccountant } from '../components/layout';
import { useResponsive } from '../hooks';
import SidebarAccountant from '../shared/components/SidebarAccountant';
import { useResponsive } from '../shared/hooks/useResponsive';
const AccountantLayout: React.FC = () => {
const { isMobile } = useResponsive();

View File

@@ -1,8 +1,9 @@
import React, { useState, Suspense, useEffect } from 'react';
import { Outlet, useLocation } from 'react-router-dom';
import { SidebarAdmin } from '../components/layout';
import { SidebarAdmin } from '../shared/components';
import { Sparkles, Zap } from 'lucide-react';
import { useResponsive } from '../hooks';
import AIAssistantWidget from '../features/ai/components/AIAssistantWidget';
// Luxury Loading Overlay
const LuxuryLoadingOverlay: React.FC = () => {
@@ -106,6 +107,9 @@ const AdminLayout: React.FC = () => {
</div>
</main>
{/* AI Assistant Widget */}
<AIAssistantWidget />
{/* Custom CSS for shimmer animation */}
<style>{`
@keyframes shimmer {

View File

@@ -1,416 +0,0 @@
import React, { useState, useEffect } from 'react';
import { useParams, Link, useNavigate } from 'react-router-dom';
import { Calendar, User, Tag, ArrowLeft, Eye, Share2, ArrowRight } from 'lucide-react';
import { blogService, BlogPost } from '../services/api';
import Loading from '../components/common/Loading';
import { createSanitizedHtml } from '../utils/htmlSanitizer';
const BlogDetailPage: React.FC = () => {
const { slug } = useParams<{ slug: string }>();
const navigate = useNavigate();
const [post, setPost] = useState<BlogPost | null>(null);
const [loading, setLoading] = useState(true);
const [relatedPosts, setRelatedPosts] = useState<BlogPost[]>([]);
useEffect(() => {
if (slug) {
fetchPost();
}
}, [slug]);
const fetchPost = async () => {
try {
setLoading(true);
const response = await blogService.getBlogPostBySlug(slug!);
if (response.status === 'success' && response.data) {
const postData = response.data.post;
setPost(postData);
// Set meta tags
if (postData.meta_title) {
document.title = postData.meta_title;
}
if (postData.meta_description) {
let metaDescription = document.querySelector('meta[name="description"]');
if (!metaDescription) {
metaDescription = document.createElement('meta');
metaDescription.setAttribute('name', 'description');
document.head.appendChild(metaDescription);
}
metaDescription.setAttribute('content', postData.meta_description);
}
// Fetch related posts
if (postData.tags && postData.tags.length > 0) {
fetchRelatedPosts(postData.tags[0]);
}
}
} catch (error: any) {
console.error('Error fetching blog post:', error);
if (error.response?.status === 404) {
navigate('/blog');
}
} finally {
setLoading(false);
}
};
const fetchRelatedPosts = async (tag: string) => {
try {
const response = await blogService.getBlogPosts({
page: 1,
limit: 3,
tag,
published_only: true,
});
if (response.status === 'success' && response.data) {
// Filter out current post
const related = response.data.posts.filter((p: BlogPost) => p.slug !== slug);
setRelatedPosts(related.slice(0, 3));
}
} catch (error) {
console.error('Error fetching related posts:', error);
}
};
const formatDate = (dateString?: string) => {
if (!dateString) return '';
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
};
const handleShare = async () => {
if (navigator.share && post) {
try {
await navigator.share({
title: post.title,
text: post.excerpt,
url: window.location.href,
});
} catch (error) {
// User cancelled or error occurred
}
} else {
// Fallback: copy to clipboard
navigator.clipboard.writeText(window.location.href);
alert('Link copied to clipboard!');
}
};
if (loading) {
return <Loading />;
}
if (!post) {
return (
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-black text-white flex items-center justify-center">
<div className="text-center">
<h1 className="text-2xl font-bold mb-4">Post not found</h1>
<Link to="/blog" className="text-[#d4af37] hover:underline">
Back to Blog
</Link>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-full" style={{ width: '100vw', position: 'relative', left: '50%', right: '50%', marginLeft: '-50vw', marginRight: '-50vw', marginTop: '-1.5rem', marginBottom: '-1.5rem' }}>
{/* Hero Section with Featured Image */}
{post.featured_image && (
<div className="relative w-full h-64 sm:h-80 lg:h-96 overflow-hidden">
<img
src={post.featured_image}
alt={post.title}
className="w-full h-full object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black via-black/50 to-transparent"></div>
</div>
)}
{/* Main Content */}
<div className="w-full py-8 sm:py-12 lg:py-16">
<div className="w-full max-w-[1920px] mx-auto px-3 sm:px-4 md:px-6 lg:px-8 xl:px-12 2xl:px-16 3xl:px-20">
<div className="max-w-4xl mx-auto">
{/* Back Button */}
<Link
to="/blog"
className="inline-flex items-center gap-2 text-gray-400 hover:text-[#d4af37] transition-colors mb-8"
>
<ArrowLeft className="w-4 h-4" />
<span>Back to Blog</span>
</Link>
{/* Article Header */}
<div className="mb-8">
{post.tags && post.tags.length > 0 && (
<div className="flex flex-wrap gap-2 mb-4">
{post.tags.map((tag) => (
<Link
key={tag}
to={`/blog?tag=${encodeURIComponent(tag)}`}
className="inline-flex items-center gap-1 px-3 py-1 bg-[#d4af37]/10 text-[#d4af37] rounded-full text-sm font-medium hover:bg-[#d4af37]/20 transition-colors"
>
<Tag className="w-3 h-3" />
{tag}
</Link>
))}
</div>
)}
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-display font-bold text-white mb-6 leading-tight">
{post.title}
</h1>
{post.excerpt && (
<p className="text-xl text-gray-300 mb-6 font-light leading-relaxed">
{post.excerpt}
</p>
)}
<div className="flex flex-wrap items-center gap-6 text-sm text-gray-400 pb-6 border-b border-[#d4af37]/20">
{post.published_at && (
<div className="flex items-center gap-2">
<Calendar className="w-4 h-4" />
<span>{formatDate(post.published_at)}</span>
</div>
)}
{post.author_name && (
<div className="flex items-center gap-2">
<User className="w-4 h-4" />
<span>{post.author_name}</span>
</div>
)}
<div className="flex items-center gap-2">
<Eye className="w-4 h-4" />
<span>{post.views_count} views</span>
</div>
<button
onClick={handleShare}
className="flex items-center gap-2 text-[#d4af37] hover:text-[#f5d76e] transition-colors"
>
<Share2 className="w-4 h-4" />
<span>Share</span>
</button>
</div>
</div>
{/* Article Content */}
<article className="prose prose-invert prose-lg max-w-none mb-12
prose-headings:text-white prose-headings:font-elegant prose-headings:font-semibold
prose-h2:text-3xl prose-h2:mt-12 prose-h2:mb-6 prose-h2:border-b prose-h2:border-[#d4af37]/20 prose-h2:pb-3
prose-h3:text-2xl prose-h3:mt-8 prose-h3:mb-4
prose-p:text-gray-300 prose-p:font-light prose-p:leading-relaxed prose-p:mb-6
prose-ul:text-gray-300 prose-ul:font-light prose-ul:my-6
prose-ol:text-gray-300 prose-ol:font-light prose-ol:my-6
prose-li:text-gray-300 prose-li:mb-2
prose-strong:text-[#d4af37] prose-strong:font-medium
prose-a:text-[#d4af37] prose-a:no-underline hover:prose-a:underline
prose-img:rounded-xl prose-img:shadow-2xl
[&_*]:text-gray-300 [&_h1]:text-white [&_h2]:text-white [&_h3]:text-white [&_h4]:text-white
[&_strong]:text-[#d4af37] [&_b]:text-[#d4af37] [&_a]:text-[#d4af37]"
>
<div
dangerouslySetInnerHTML={createSanitizedHtml(
post.content || '<p>No content available.</p>'
)}
/>
</article>
{/* Luxury Sections */}
{post.sections && post.sections.length > 0 && (
<div className="space-y-16 mb-12">
{post.sections
.filter((section) => section.is_visible !== false) // Only show visible sections
.map((section, index) => (
<div key={index}>
{/* Hero Section */}
{section.type === 'hero' && (
<div className="relative rounded-3xl overflow-hidden border-2 border-[#d4af37]/20 shadow-2xl">
{section.image && (
<div className="absolute inset-0">
<img src={section.image} alt={section.title} className="w-full h-full object-cover" />
<div className="absolute inset-0 bg-gradient-to-t from-black/90 via-black/50 to-transparent"></div>
</div>
)}
<div className="relative px-8 py-16 md:px-16 md:py-24 text-center">
{section.title && (
<h2 className="text-4xl md:text-5xl lg:text-6xl font-serif font-bold text-white mb-6">
{section.title}
</h2>
)}
{section.content && (
<p className="text-xl md:text-2xl text-gray-200 font-light leading-relaxed max-w-3xl mx-auto">
{section.content}
</p>
)}
</div>
</div>
)}
{/* Text Section */}
{section.type === 'text' && (
<div className={`bg-gradient-to-br from-[#1a1a1a] to-[#0f0f0f] rounded-3xl border-2 border-[#d4af37]/20 p-8 md:p-12 shadow-xl ${
section.alignment === 'center' ? 'text-center' : section.alignment === 'right' ? 'text-right' : 'text-left'
}`}>
{section.title && (
<h3 className="text-3xl md:text-4xl font-serif font-bold text-white mb-6">
{section.title}
</h3>
)}
{section.content && (
<div
className="text-gray-300 font-light leading-relaxed text-lg"
dangerouslySetInnerHTML={createSanitizedHtml(section.content)}
/>
)}
</div>
)}
{/* Image Section */}
{section.type === 'image' && section.image && (
<div className="rounded-3xl overflow-hidden border-2 border-[#d4af37]/20 shadow-2xl">
<img src={section.image} alt={section.title || 'Blog image'} className="w-full h-auto" />
{section.title && (
<div className="bg-gradient-to-br from-[#1a1a1a] to-[#0f0f0f] px-6 py-4 border-t border-[#d4af37]/20">
<p className="text-gray-400 text-sm font-light italic text-center">{section.title}</p>
</div>
)}
</div>
)}
{/* Gallery Section */}
{section.type === 'gallery' && section.images && section.images.length > 0 && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{section.images.map((img, imgIndex) => (
<div key={imgIndex} className="rounded-2xl overflow-hidden border-2 border-[#d4af37]/20 shadow-xl group hover:border-[#d4af37]/50 transition-all">
<img src={img} alt={`Gallery image ${imgIndex + 1}`} className="w-full h-64 object-cover group-hover:scale-110 transition-transform duration-500" />
</div>
))}
</div>
)}
{/* Quote Section */}
{section.type === 'quote' && (
<div className="relative bg-gradient-to-br from-[#1a1a1a] to-[#0f0f0f] rounded-3xl border-2 border-[#d4af37]/30 p-8 md:p-12 shadow-2xl">
<div className="absolute top-6 left-6 text-6xl text-[#d4af37]/20 font-serif">"</div>
{section.quote && (
<blockquote className="text-2xl md:text-3xl font-serif font-light text-white italic mb-6 relative z-10 pl-8">
{section.quote}
</blockquote>
)}
{section.author && (
<cite className="text-[#d4af37] text-lg font-medium not-italic block text-right">
— {section.author}
</cite>
)}
</div>
)}
{/* Features Section */}
{section.type === 'features' && section.features && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{section.features.map((feature, featIndex) => (
<div key={featIndex} className="bg-gradient-to-br from-[#1a1a1a] to-[#0f0f0f] rounded-2xl border-2 border-[#d4af37]/20 p-6 shadow-xl hover:border-[#d4af37]/50 transition-all">
{feature.icon && (
<div className="text-4xl mb-4">{feature.icon}</div>
)}
<h4 className="text-xl font-bold text-white mb-3">{feature.title}</h4>
<p className="text-gray-300 font-light leading-relaxed">{feature.description}</p>
</div>
))}
</div>
)}
{/* CTA Section */}
{section.type === 'cta' && (
<div className="relative bg-gradient-to-br from-[#d4af37]/10 via-[#c9a227]/5 to-[#d4af37]/10 rounded-3xl border-2 border-[#d4af37]/30 p-8 md:p-12 text-center shadow-2xl">
{section.title && (
<h3 className="text-3xl md:text-4xl font-serif font-bold text-white mb-4">
{section.title}
</h3>
)}
{section.content && (
<p className="text-xl text-gray-300 font-light mb-8 max-w-2xl mx-auto">
{section.content}
</p>
)}
{section.cta_text && section.cta_link && (
<a
href={section.cta_link}
className="inline-flex items-center gap-3 px-8 py-4 bg-gradient-to-r from-[#d4af37] to-[#c9a227] text-[#0f0f0f] font-semibold rounded-xl hover:from-[#f5d76e] hover:to-[#d4af37] transition-all shadow-lg hover:shadow-xl hover:scale-105"
>
{section.cta_text}
<ArrowRight className="w-5 h-5" />
</a>
)}
</div>
)}
{/* Video Section */}
{section.type === 'video' && section.video_url && (
<div className="rounded-3xl overflow-hidden border-2 border-[#d4af37]/20 shadow-2xl bg-black">
<div className="aspect-video">
<iframe
src={section.video_url.replace('watch?v=', 'embed/').replace('vimeo.com/', 'player.vimeo.com/video/')}
className="w-full h-full"
allowFullScreen
title="Blog video"
/>
</div>
</div>
)}
</div>
))}
</div>
)}
{/* Related Posts */}
{relatedPosts.length > 0 && (
<div className="mt-16 pt-12 border-t border-[#d4af37]/20">
<h2 className="text-2xl sm:text-3xl font-bold text-white mb-8">Related Posts</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{relatedPosts.map((relatedPost) => (
<Link
key={relatedPost.id}
to={`/blog/${relatedPost.slug}`}
className="group bg-gradient-to-br from-[#1a1a1a] to-[#0f0f0f] rounded-xl border border-[#d4af37]/20 overflow-hidden hover:border-[#d4af37]/50 transition-all duration-300"
>
{relatedPost.featured_image && (
<div className="relative h-40 overflow-hidden">
<img
src={relatedPost.featured_image}
alt={relatedPost.title}
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
/>
</div>
)}
<div className="p-4">
<h3 className="text-lg font-bold text-white mb-2 group-hover:text-[#d4af37] transition-colors line-clamp-2">
{relatedPost.title}
</h3>
{relatedPost.excerpt && (
<p className="text-gray-400 text-sm line-clamp-2 font-light">
{relatedPost.excerpt}
</p>
)}
</div>
</Link>
))}
</div>
</div>
)}
</div>
</div>
</div>
</div>
);
};
export default BlogDetailPage;

View File

@@ -1,321 +0,0 @@
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { Calendar, User, Tag, Search, ArrowRight, Eye, Sparkles, BookOpen } from 'lucide-react';
import { blogService, BlogPost } from '../services/api';
import Loading from '../components/common/Loading';
import Pagination from '../components/common/Pagination';
const BlogPage: React.FC = () => {
const [posts, setPosts] = useState<BlogPost[]>([]);
const [loading, setLoading] = useState(true);
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const [total, setTotal] = useState(0);
const [searchTerm, setSearchTerm] = useState('');
const [selectedTag, setSelectedTag] = useState<string | null>(null);
const [allTags, setAllTags] = useState<string[]>([]);
useEffect(() => {
fetchPosts();
}, [currentPage, searchTerm, selectedTag]);
const fetchPosts = async () => {
try {
setLoading(true);
const response = await blogService.getBlogPosts({
page: currentPage,
limit: 10,
search: searchTerm || undefined,
tag: selectedTag || undefined,
published_only: true,
});
if (response.status === 'success' && response.data) {
setPosts(response.data.posts);
setTotalPages(response.data.pagination.pages);
setTotal(response.data.pagination.total);
// Use all_tags from API response if available, otherwise extract from current page
if (response.data.all_tags && Array.isArray(response.data.all_tags)) {
setAllTags(response.data.all_tags);
} else {
// Fallback: extract tags from current page posts
const tags = new Set<string>();
response.data.posts.forEach((post: BlogPost) => {
post.tags?.forEach(tag => tags.add(tag));
});
setAllTags(Array.from(tags));
}
}
} catch (error: any) {
console.error('Error fetching blog posts:', error);
} finally {
setLoading(false);
}
};
const formatDate = (dateString?: string) => {
if (!dateString) return '';
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
};
if (loading && posts.length === 0) {
return <Loading />;
}
return (
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-full" style={{ width: '100vw', position: 'relative', left: '50%', right: '50%', marginLeft: '-50vw', marginRight: '-50vw', marginTop: '-1.5rem', marginBottom: '-1.5rem' }}>
{/* Hero Section */}
<div className="w-full bg-gradient-to-br from-[#1a1a1a] via-[#0f0f0f] to-[#1a1a1a] border-b border-[#d4af37]/10 pt-6 sm:pt-7 md:pt-8 overflow-hidden relative">
{/* Background Effects */}
<div className="absolute inset-0 opacity-10">
<div className="absolute top-10 left-10 w-32 sm:w-48 h-32 sm:h-48 bg-[#d4af37] rounded-full blur-3xl"></div>
<div className="absolute bottom-10 right-10 w-40 sm:w-64 h-40 sm:h-64 bg-[#c9a227] rounded-full blur-3xl"></div>
</div>
<div className="absolute top-0 left-0 w-full h-px bg-gradient-to-r from-transparent via-[#d4af37]/50 to-transparent"></div>
<div className="w-full max-w-[1920px] mx-auto px-3 sm:px-4 md:px-6 lg:px-8 xl:px-12 2xl:px-16 3xl:px-20 py-4 sm:py-5 md:py-6 relative z-10">
<div className="max-w-2xl mx-auto text-center px-2">
<div className="flex justify-center mb-2 sm:mb-3 md:mb-4">
<div className="relative group">
<div className="absolute inset-0 bg-gradient-to-br from-[#d4af37] via-[#f5d76e] to-[#c9a227] rounded-xl blur-lg opacity-40 group-hover:opacity-60 transition-opacity duration-500"></div>
<div className="relative p-2 sm:p-2.5 md:p-3 bg-gradient-to-br from-[#1a1a1a] to-[#0a0a0a] rounded-lg border-2 border-[#d4af37]/40 backdrop-blur-sm shadow-xl shadow-[#d4af37]/20 group-hover:border-[#d4af37]/60 transition-all duration-300">
<BookOpen className="w-5 h-5 sm:w-6 sm:h-6 md:w-7 md:h-7 text-[#d4af37] drop-shadow-lg" />
</div>
</div>
</div>
<h1 className="text-2xl xs:text-3xl sm:text-4xl md:text-5xl font-serif font-semibold mb-2 sm:mb-3 tracking-tight leading-tight px-2">
<span className="bg-gradient-to-r from-white via-[#d4af37] to-white bg-clip-text text-transparent">
Our Blog
</span>
</h1>
<div className="w-12 sm:w-16 md:w-20 h-0.5 bg-gradient-to-r from-transparent via-[#d4af37] to-transparent mx-auto mb-2 sm:mb-3"></div>
<p className="text-sm sm:text-base md:text-lg text-gray-300 font-light leading-relaxed max-w-xl mx-auto tracking-wide px-2 sm:px-4">
Discover stories, insights, and updates from our luxury hotel
</p>
<div className="mt-4 flex items-center justify-center gap-2 text-[#d4af37]/60">
<Sparkles className="w-4 h-4 animate-pulse" />
<span className="text-xs sm:text-sm font-light tracking-wider uppercase">Premium Content</span>
<Sparkles className="w-4 h-4 animate-pulse" style={{ animationDelay: '0.5s' }} />
</div>
</div>
</div>
<div className="absolute bottom-0 left-0 w-full h-px bg-gradient-to-r from-transparent via-[#d4af37]/30 to-transparent"></div>
</div>
{/* Main Content - Full Width */}
<div className="w-full py-12 sm:py-16 lg:py-20">
<div className="w-full max-w-[1920px] mx-auto px-3 sm:px-4 md:px-6 lg:px-8 xl:px-12 2xl:px-16 3xl:px-20">
{/* Search Section */}
<div className="mb-12 sm:mb-16">
<div className="flex flex-col lg:flex-row gap-6 mb-8">
<div className="flex-1 relative group">
<div className="absolute inset-0 bg-gradient-to-r from-[#d4af37]/10 to-transparent rounded-xl blur-xl opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
<div className="relative">
<Search className="absolute left-5 top-1/2 transform -translate-y-1/2 text-[#d4af37] w-5 h-5 z-10" />
<input
type="text"
placeholder="Search blog posts..."
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value);
setCurrentPage(1);
}}
className="w-full pl-14 pr-5 py-4 bg-gradient-to-br from-[#1a1a1a] to-[#0f0f0f] border border-[#d4af37]/20 rounded-xl text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[#d4af37]/50 focus:border-[#d4af37]/50 transition-all duration-300 backdrop-blur-sm font-light"
/>
</div>
</div>
</div>
</div>
{/* Main Layout: Posts on Left, Tags on Right */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 lg:gap-12">
{/* Blog Posts Section */}
<div className="lg:col-span-9">
{/* Results Count */}
{!loading && total > 0 && (
<div className="mb-8 text-gray-400 font-light text-sm">
Showing {posts.length} of {total} posts
</div>
)}
{/* Blog Posts Grid - Luxury Design */}
{posts.length === 0 ? (
<div className="text-center py-20">
<div className="inline-flex items-center justify-center w-20 h-20 rounded-full bg-[#d4af37]/10 mb-6">
<BookOpen className="w-10 h-10 text-[#d4af37]" />
</div>
<p className="text-gray-400 text-xl font-light">No blog posts found</p>
<p className="text-gray-500 text-sm mt-2">Try adjusting your search or filters</p>
</div>
) : (
<>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 lg:gap-10 mb-12">
{posts.map((post, index) => (
<Link
key={post.id}
to={`/blog/${post.slug}`}
className="group relative bg-gradient-to-br from-[#1a1a1a] via-[#0f0f0f] to-[#0a0a0a] rounded-3xl border-2 border-[#d4af37]/20 overflow-hidden hover:border-[#d4af37]/60 transition-all duration-700 hover:shadow-2xl hover:shadow-[#d4af37]/30 hover:-translate-y-3"
style={{ animationDelay: `${index * 50}ms` }}
>
{/* Premium Glow Effects */}
<div className="absolute inset-0 bg-gradient-to-br from-[#d4af37]/0 via-[#d4af37]/0 to-[#d4af37]/0 group-hover:from-[#d4af37]/10 group-hover:via-[#d4af37]/5 group-hover:to-[#d4af37]/10 transition-all duration-700 rounded-3xl blur-2xl opacity-0 group-hover:opacity-100"></div>
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-transparent via-[#d4af37]/50 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
{/* Content */}
<div className="relative z-10">
{post.featured_image && (
<div className="relative h-72 sm:h-80 overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-t from-black/90 via-black/40 to-transparent z-10"></div>
<img
src={post.featured_image}
alt={post.title}
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-1000 ease-out"
/>
{/* Premium Badge Overlay */}
<div className="absolute top-6 left-6 z-20">
<div className="bg-gradient-to-br from-[#d4af37]/20 to-[#c9a227]/10 backdrop-blur-md rounded-2xl px-4 py-2 border border-[#d4af37]/40 shadow-xl">
<div className="flex items-center gap-2 text-[#d4af37]">
<Eye className="w-4 h-4" />
<span className="text-sm font-semibold">{post.views_count}</span>
</div>
</div>
</div>
{/* Luxury Corner Accent */}
<div className="absolute top-0 right-0 w-32 h-32 bg-gradient-to-br from-[#d4af37]/20 to-transparent rounded-bl-full opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
</div>
)}
<div className="p-8">
{post.tags && post.tags.length > 0 && (
<div className="flex flex-wrap gap-2 mb-5">
{post.tags.slice(0, 2).map((tag) => (
<span
key={tag}
className="inline-flex items-center gap-1.5 px-4 py-1.5 bg-gradient-to-br from-[#d4af37]/15 to-[#d4af37]/5 text-[#d4af37] rounded-full text-xs font-semibold border border-[#d4af37]/30 backdrop-blur-sm shadow-lg"
>
<Tag className="w-3 h-3" />
{tag}
</span>
))}
</div>
)}
<h2 className="text-2xl sm:text-3xl font-serif font-bold text-white mb-4 group-hover:text-[#d4af37] transition-colors duration-500 line-clamp-2 leading-tight tracking-tight">
{post.title}
</h2>
{post.excerpt && (
<p className="text-gray-300 text-base mb-6 line-clamp-3 font-light leading-relaxed">
{post.excerpt}
</p>
)}
<div className="flex items-center justify-between text-sm text-gray-400 pt-6 border-t border-[#d4af37]/20 mb-6">
<div className="flex items-center gap-4">
{post.published_at && (
<div className="flex items-center gap-2">
<Calendar className="w-4 h-4 text-[#d4af37]" />
<span className="font-light">{formatDate(post.published_at)}</span>
</div>
)}
</div>
{post.author_name && (
<div className="flex items-center gap-2">
<User className="w-4 h-4 text-[#d4af37]" />
<span className="font-light">{post.author_name}</span>
</div>
)}
</div>
<div className="flex items-center justify-between pt-4 border-t border-[#d4af37]/10">
<div className="flex items-center gap-3 text-[#d4af37] group-hover:gap-4 transition-all duration-300">
<span className="text-sm font-semibold tracking-wide uppercase">Read Article</span>
<ArrowRight className="w-5 h-5 group-hover:translate-x-2 transition-transform duration-300" />
</div>
<div className="w-12 h-0.5 bg-gradient-to-r from-[#d4af37] to-transparent group-hover:w-20 transition-all duration-500"></div>
</div>
</div>
</div>
</Link>
))}
</div>
{/* Pagination */}
{totalPages > 1 && (
<div className="mt-16 flex justify-center">
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={setCurrentPage}
/>
</div>
)}
</>
)}
</div>
{/* Tags Sidebar - Right Side */}
<div className="lg:col-span-3">
{allTags.length > 0 && (
<div className="sticky top-8">
<div className="bg-gradient-to-br from-[#1a1a1a] via-[#0f0f0f] to-[#0a0a0a] rounded-3xl border-2 border-[#d4af37]/20 p-8 backdrop-blur-xl shadow-2xl">
<div className="flex items-center gap-3 mb-6 pb-6 border-b border-[#d4af37]/20">
<div className="w-1 h-8 bg-gradient-to-b from-[#d4af37] to-[#c9a227] rounded-full"></div>
<h3 className="text-xl font-serif font-bold text-white">Filter by Tags</h3>
</div>
<div className="space-y-3">
<button
onClick={() => {
setSelectedTag(null);
setCurrentPage(1);
}}
className={`group relative w-full text-left px-5 py-4 rounded-2xl text-sm font-medium transition-all duration-300 overflow-hidden ${
selectedTag === null
? 'bg-gradient-to-r from-[#d4af37] to-[#c9a227] text-[#0f0f0f] shadow-lg shadow-[#d4af37]/40'
: 'bg-gradient-to-br from-[#0f0f0f] to-[#0a0a0a] text-gray-300 border-2 border-[#d4af37]/20 hover:border-[#d4af37]/50 hover:text-[#d4af37] hover:bg-[#1a1a1a]'
}`}
>
<span className="relative z-10 flex items-center gap-2">
<Tag className="w-4 h-4" />
All Posts
</span>
{selectedTag === null && (
<div className="absolute inset-0 bg-gradient-to-r from-white/20 to-transparent animate-shimmer"></div>
)}
</button>
{allTags.map((tag) => (
<button
key={tag}
onClick={() => {
setSelectedTag(tag);
setCurrentPage(1);
}}
className={`group relative w-full text-left px-5 py-4 rounded-2xl text-sm font-medium transition-all duration-300 overflow-hidden ${
selectedTag === tag
? 'bg-gradient-to-r from-[#d4af37] to-[#c9a227] text-[#0f0f0f] shadow-lg shadow-[#d4af37]/40'
: 'bg-gradient-to-br from-[#0f0f0f] to-[#0a0a0a] text-gray-300 border-2 border-[#d4af37]/20 hover:border-[#d4af37]/50 hover:text-[#d4af37] hover:bg-[#1a1a1a]'
}`}
>
<span className="relative z-10 flex items-center gap-2">
<Tag className="w-4 h-4" />
{tag}
</span>
{selectedTag === tag && (
<div className="absolute inset-0 bg-gradient-to-r from-white/20 to-transparent animate-shimmer"></div>
)}
</button>
))}
</div>
</div>
</div>
)}
</div>
</div>
</div>
</div>
</div>
);
};
export default BlogPage;

View File

@@ -1,180 +0,0 @@
import React, { useState, useEffect } from 'react';
import { XCircle, ArrowLeft } from 'lucide-react';
import { Link } from 'react-router-dom';
import { pageContentService } from '../services/api';
import type { PageContent } from '../services/api/pageContentService';
import { useCompanySettings } from '../contexts/CompanySettingsContext';
import Loading from '../components/common/Loading';
import { createSanitizedHtml } from '../utils/htmlSanitizer';
const CancellationPolicyPage: React.FC = () => {
const { settings } = useCompanySettings();
const [pageContent, setPageContent] = useState<PageContent | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchPageContent = async () => {
try {
setLoading(true);
const response = await pageContentService.getCancellationContent();
if (response.status === 'success' && response.data?.page_content) {
const content = response.data.page_content;
// Process HTML content to ensure text is visible
if (content.content) {
const tempDiv = document.createElement('div');
tempDiv.innerHTML = content.content;
const allElements = tempDiv.querySelectorAll('*');
allElements.forEach((el) => {
const htmlEl = el as HTMLElement;
const tagName = htmlEl.tagName.toLowerCase();
const currentColor = htmlEl.style.color;
if (!currentColor || currentColor === 'black' || currentColor === '#000' || currentColor === '#000000') {
if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName)) {
htmlEl.style.color = '#ffffff';
} else if (['strong', 'b'].includes(tagName)) {
htmlEl.style.color = '#d4af37';
} else if (tagName === 'a') {
htmlEl.style.color = '#d4af37';
} else {
htmlEl.style.color = '#d1d5db';
}
}
});
content.content = tempDiv.innerHTML;
}
setPageContent(content);
if (content.meta_title) {
document.title = content.meta_title;
}
if (content.meta_description) {
let metaDescription = document.querySelector('meta[name="description"]');
if (!metaDescription) {
metaDescription = document.createElement('meta');
metaDescription.setAttribute('name', 'description');
document.head.appendChild(metaDescription);
}
metaDescription.setAttribute('content', content.meta_description);
}
}
} catch (err: any) {
console.error('Error fetching page content:', err);
// If page is disabled (404), set pageContent to null to show disabled message
if (err.response?.status === 404) {
setPageContent(null);
}
} finally {
setLoading(false);
}
};
fetchPageContent();
}, []);
if (loading) {
return <Loading />;
}
if (!pageContent) {
return (
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-screen relative -mt-6 -mb-6 flex items-center justify-center"
style={{
marginLeft: 'calc(50% - 50vw)',
marginRight: 'calc(50% - 50vw)',
width: '100vw',
paddingTop: '2rem',
paddingBottom: '2rem',
zIndex: 1
}}
>
<div className="text-center">
<XCircle className="w-16 h-16 text-[#d4af37]/50 mx-auto mb-4" />
<h1 className="text-2xl font-elegant font-bold text-white mb-2">Cancellation Policy</h1>
<p className="text-gray-400">This page is currently unavailable.</p>
<Link
to="/"
className="inline-flex items-center gap-2 text-[#d4af37]/80 hover:text-[#d4af37] mt-6 transition-all duration-300"
>
<ArrowLeft className="w-4 h-4" />
<span>Back to Home</span>
</Link>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-screen relative -mt-6 -mb-6"
style={{
marginLeft: 'calc(50% - 50vw)',
marginRight: 'calc(50% - 50vw)',
width: '100vw',
paddingTop: '2rem',
paddingBottom: '2rem',
zIndex: 1
}}
>
<div className="w-full px-3 sm:px-4 md:px-6 lg:px-8 py-8 sm:py-12 max-w-5xl mx-auto">
<Link
to="/"
className="inline-flex items-center gap-2 text-[#d4af37]/80 hover:text-[#d4af37] mb-6 transition-all duration-300 group font-light tracking-wide text-xs sm:text-sm"
>
<ArrowLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" />
<span>Back to Home</span>
</Link>
<div className="mb-8 sm:mb-12 text-center">
<div className="inline-flex items-center justify-center w-16 h-16 sm:w-20 sm:h-20 mb-4 sm:mb-6 bg-gradient-to-br from-[#d4af37]/20 to-[#c9a227]/10 rounded-full border border-[#d4af37]/30">
<XCircle className="w-8 h-8 sm:w-10 sm:h-10 text-[#d4af37]" />
</div>
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-elegant font-bold text-white mb-3 sm:mb-4 tracking-tight leading-tight bg-gradient-to-r from-white via-[#d4af37] to-white bg-clip-text text-transparent">
{pageContent.title || 'Cancellation Policy'}
</h1>
{pageContent.subtitle && (
<p className="text-base sm:text-lg text-gray-300 font-light tracking-wide">
{pageContent.subtitle}
</p>
)}
</div>
<div className="bg-gradient-to-br from-[#1a1a1a] to-[#0a0a0a] rounded-xl border border-[#d4af37]/20 backdrop-blur-xl shadow-2xl shadow-[#d4af37]/5 p-6 sm:p-8 lg:p-12">
<div
className="prose prose-invert prose-lg max-w-none text-gray-300
prose-headings:text-white prose-headings:font-elegant prose-headings:font-semibold
prose-h2:text-2xl prose-h2:mt-8 prose-h2:mb-4 prose-h2:border-b prose-h2:border-[#d4af37]/20 prose-h2:pb-2
prose-p:text-gray-300 prose-p:font-light prose-p:leading-relaxed prose-p:mb-4
prose-ul:text-gray-300 prose-ul:font-light prose-ul:my-4
prose-li:text-gray-300 prose-li:mb-2 prose-li:ml-4
prose-strong:text-[#d4af37] prose-strong:font-medium
prose-a:text-[#d4af37] prose-a:no-underline hover:prose-a:underline
[&_*]:text-gray-300 [&_h1]:text-white [&_h2]:text-white [&_h3]:text-white [&_h4]:text-white [&_h5]:text-white [&_h6]:text-white
[&_strong]:text-[#d4af37] [&_b]:text-[#d4af37] [&_a]:text-[#d4af37]"
style={{ color: '#d1d5db' }}
dangerouslySetInnerHTML={createSanitizedHtml(
pageContent.content || pageContent.description || '<p style="color: #d1d5db;">No content available.</p>'
)}
/>
</div>
{settings.company_email && (
<div className="mt-8 text-center">
<p className="text-sm text-gray-400 font-light">
For questions about cancellations, contact us at{' '}
<a href={`mailto:${settings.company_email}`} className="text-[#d4af37] hover:underline">
{settings.company_email}
</a>
</p>
</div>
)}
</div>
</div>
);
};
export default CancellationPolicyPage;

View File

@@ -1,518 +0,0 @@
import React, { useState, useEffect } from 'react';
import { Mail, Phone, MapPin, Send, User, MessageSquare } from 'lucide-react';
import { submitContactForm } from '../services/api/contactService';
import { pageContentService } from '../services/api';
import type { PageContent } from '../services/api/pageContentService';
import { useCompanySettings } from '../contexts/CompanySettingsContext';
import { toast } from 'react-toastify';
import Recaptcha from '../components/common/Recaptcha';
import { recaptchaService } from '../services/api/systemSettingsService';
import ChatWidget from '../components/chat/ChatWidget';
import { useAntibotForm } from '../hooks/useAntibotForm';
import HoneypotField from '../components/common/HoneypotField';
const ContactPage: React.FC = () => {
const { settings } = useCompanySettings();
const [pageContent, setPageContent] = useState<PageContent | null>(null);
const [formData, setFormData] = useState({
name: '',
email: '',
phone: '',
subject: '',
message: '',
});
const [loading, setLoading] = useState(false);
const [errors, setErrors] = useState<Record<string, string>>({});
// Enhanced antibot protection
const {
honeypotValue,
setHoneypotValue,
recaptchaToken,
setRecaptchaToken,
validate: validateAntibot,
rateLimitInfo,
} = useAntibotForm({
formId: 'contact',
minTimeOnPage: 5000,
minTimeToFill: 3000,
requireRecaptcha: false,
maxAttempts: 5,
onValidationError: (errors) => {
errors.forEach((err) => toast.error(err));
},
});
const validateForm = (): boolean => {
const newErrors: Record<string, string> = {};
if (!formData.name.trim()) {
newErrors.name = 'Name is required';
}
if (!formData.email.trim()) {
newErrors.email = 'Email is required';
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
newErrors.email = 'Please enter a valid email address';
}
if (!formData.subject.trim()) {
newErrors.subject = 'Subject is required';
}
if (!formData.message.trim()) {
newErrors.message = 'Message is required';
} else if (formData.message.trim().length < 10) {
newErrors.message = 'Message must be at least 10 characters long';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!validateForm()) {
return;
}
// Validate antibot protection
const isValid = await validateAntibot();
if (!isValid) {
return;
}
if (recaptchaToken) {
try {
const verifyResponse = await recaptchaService.verifyRecaptcha(recaptchaToken);
if (verifyResponse.status === 'error' || !verifyResponse.data.verified) {
toast.error('reCAPTCHA verification failed. Please try again.');
setRecaptchaToken(null);
return;
}
} catch (error) {
toast.error('reCAPTCHA verification failed. Please try again.');
setRecaptchaToken(null);
return;
}
}
setLoading(true);
try {
await submitContactForm(formData);
toast.success('Thank you for contacting us! We will get back to you soon.');
setFormData({
name: '',
email: '',
phone: '',
subject: '',
message: '',
});
setErrors({});
setRecaptchaToken(null);
} catch (error: any) {
const errorMessage = error?.response?.data?.detail || error?.message || 'Failed to send message. Please try again.';
toast.error(errorMessage);
setRecaptchaToken(null);
} finally {
setLoading(false);
}
};
useEffect(() => {
const fetchPageContent = async () => {
try {
const response = await pageContentService.getContactContent();
if (response.status === 'success' && response.data?.page_content) {
setPageContent(response.data.page_content);
if (response.data.page_content.meta_title) {
document.title = response.data.page_content.meta_title;
}
if (response.data.page_content.meta_description) {
let metaDescription = document.querySelector('meta[name="description"]');
if (!metaDescription) {
metaDescription = document.createElement('meta');
metaDescription.setAttribute('name', 'description');
document.head.appendChild(metaDescription);
}
metaDescription.setAttribute('content', response.data.page_content.meta_description);
}
}
} catch (err: any) {
console.error('Error fetching page content:', err);
}
};
fetchPageContent();
}, []);
const displayPhone = settings.company_phone || 'Available 24/7 for your convenience';
const displayEmail = settings.company_email || "We'll respond within 24 hours";
const displayAddress = settings.company_address || 'Visit us at our hotel reception';
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { name, value } = e.target;
setFormData((prev) => ({ ...prev, [name]: value }));
if (errors[name]) {
setErrors((prev) => {
const newErrors = { ...prev };
delete newErrors[name];
return newErrors;
});
}
};
return (
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-full" style={{ width: '100vw', position: 'relative', left: '50%', right: '50%', marginLeft: '-50vw', marginRight: '-50vw', marginTop: '-1.5rem', marginBottom: '-1.5rem' }}>
{}
<div className="w-full bg-gradient-to-br from-[#1a1a1a] via-[#0f0f0f] to-[#1a1a1a] border-b border-[#d4af37]/10 pt-6 sm:pt-7 md:pt-8 overflow-hidden relative">
{}
<div className="absolute inset-0 opacity-10">
<div className="absolute top-10 left-10 w-32 sm:w-48 h-32 sm:h-48 bg-[#d4af37] rounded-full blur-3xl"></div>
<div className="absolute bottom-10 right-10 w-40 sm:w-64 h-40 sm:h-64 bg-[#c9a227] rounded-full blur-3xl"></div>
</div>
<div className="absolute top-0 left-0 w-full h-px bg-gradient-to-r from-transparent via-[#d4af37]/50 to-transparent"></div>
<div className="w-full max-w-[1920px] mx-auto px-3 sm:px-4 md:px-6 lg:px-8 xl:px-12 2xl:px-16 3xl:px-20 py-4 sm:py-5 md:py-6 relative z-10">
<div className="max-w-2xl mx-auto text-center px-2">
<div className="flex justify-center mb-2 sm:mb-3 md:mb-4">
<div className="relative group">
<div className="absolute inset-0 bg-gradient-to-br from-[#d4af37] via-[#f5d76e] to-[#c9a227] rounded-xl blur-lg opacity-40 group-hover:opacity-60 transition-opacity duration-500"></div>
<div className="relative p-2 sm:p-2.5 md:p-3 bg-gradient-to-br from-[#1a1a1a] to-[#0a0a0a] rounded-lg border-2 border-[#d4af37]/40 backdrop-blur-sm shadow-xl shadow-[#d4af37]/20 group-hover:border-[#d4af37]/60 transition-all duration-300">
<Mail className="w-5 h-5 sm:w-6 sm:h-6 md:w-7 md:h-7 text-[#d4af37] drop-shadow-lg" />
</div>
</div>
</div>
<h1 className="text-2xl xs:text-3xl sm:text-4xl md:text-5xl font-serif font-semibold mb-2 sm:mb-3 tracking-tight leading-tight px-2">
<span className="bg-gradient-to-r from-white via-[#d4af37] to-white bg-clip-text text-transparent">
{pageContent?.title || 'Contact Us'}
</span>
</h1>
<div className="w-12 sm:w-16 md:w-20 h-0.5 bg-gradient-to-r from-transparent via-[#d4af37] to-transparent mx-auto mb-2 sm:mb-3"></div>
<p className="text-sm sm:text-base md:text-lg text-gray-300 font-light leading-relaxed max-w-xl mx-auto tracking-wide px-2 sm:px-4">
{pageContent?.subtitle || pageContent?.description || "Experience the pinnacle of hospitality. We're here to make your stay extraordinary."}
</p>
</div>
</div>
<div className="absolute bottom-0 left-0 w-full h-px bg-gradient-to-r from-transparent via-[#d4af37]/30 to-transparent"></div>
</div>
{}
<div className="w-full py-4 sm:py-6 md:py-8 lg:py-10 xl:py-12">
<div className="w-full max-w-[1920px] mx-auto px-3 sm:px-4 md:px-6 lg:px-8 xl:px-12 2xl:px-16 3xl:px-20">
<div className="grid grid-cols-1 lg:grid-cols-12 gap-4 sm:gap-5 md:gap-6 lg:gap-7 xl:gap-8 2xl:gap-10 max-w-7xl mx-auto">
{}
<div className="lg:col-span-4">
<div className="bg-gradient-to-br from-[#1a1a1a] via-[#0f0f0f] to-[#0a0a0a]
rounded-xl sm:rounded-2xl border-2 border-[#d4af37]/30 p-5 sm:p-6 md:p-8 lg:p-10
shadow-2xl shadow-[#d4af37]/10 backdrop-blur-xl
relative overflow-hidden h-full group hover:border-[#d4af37]/50 transition-all duration-500">
{}
<div className="absolute inset-0 bg-gradient-to-br from-[#d4af37]/5 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
<div className="relative z-10">
<div className="flex items-center gap-2 sm:gap-3 mb-6 sm:mb-7 md:mb-8">
<div className="w-0.5 sm:w-1 h-6 sm:h-8 bg-gradient-to-b from-[#d4af37] to-[#c9a227] rounded-full"></div>
<h2 className="text-xl sm:text-2xl md:text-3xl font-serif font-semibold
text-white tracking-tight">
Get in Touch
</h2>
</div>
<div className="space-y-5 sm:space-y-6 md:space-y-7">
<div className="flex items-start gap-3 sm:gap-4 md:gap-5 group/item hover:translate-x-1 transition-transform duration-300">
<div className="p-2.5 sm:p-3 md:p-4 bg-gradient-to-br from-[#d4af37]/20 to-[#d4af37]/10 rounded-lg sm:rounded-xl border border-[#d4af37]/40 flex-shrink-0 group-hover/item:bg-gradient-to-br group-hover/item:from-[#d4af37]/30 group-hover/item:to-[#d4af37]/20 group-hover/item:border-[#d4af37]/60 transition-all duration-300 shadow-lg shadow-[#d4af37]/10">
<Mail className="w-5 h-5 sm:w-6 sm:h-6 text-[#d4af37] drop-shadow-lg" />
</div>
<div>
<h3 className="text-sm sm:text-base font-medium text-[#d4af37] mb-1 sm:mb-2 tracking-wide">Email</h3>
<a href={`mailto:${displayEmail}`} className="text-gray-300 font-light text-xs sm:text-sm leading-relaxed hover:text-[#d4af37] transition-colors">
{displayEmail}
</a>
</div>
</div>
<div className="flex items-start gap-3 sm:gap-4 md:gap-5 group/item hover:translate-x-1 transition-transform duration-300">
<div className="p-2.5 sm:p-3 md:p-4 bg-gradient-to-br from-[#d4af37]/20 to-[#d4af37]/10 rounded-lg sm:rounded-xl border border-[#d4af37]/40 flex-shrink-0 group-hover/item:bg-gradient-to-br group-hover/item:from-[#d4af37]/30 group-hover/item:to-[#d4af37]/20 group-hover/item:border-[#d4af37]/60 transition-all duration-300 shadow-lg shadow-[#d4af37]/10">
<Phone className="w-5 h-5 sm:w-6 sm:h-6 text-[#d4af37] drop-shadow-lg" />
</div>
<div>
<h3 className="text-sm sm:text-base font-medium text-[#d4af37] mb-1 sm:mb-2 tracking-wide">Phone</h3>
<a href={`tel:${displayPhone.replace(/\s+/g, '').replace(/[()]/g, '')}`} className="text-gray-300 font-light text-xs sm:text-sm leading-relaxed hover:text-[#d4af37] transition-colors">
{displayPhone}
</a>
</div>
</div>
<div className="flex items-start gap-3 sm:gap-4 md:gap-5 group/item hover:translate-x-1 transition-transform duration-300">
<div className="p-2.5 sm:p-3 md:p-4 bg-gradient-to-br from-[#d4af37]/20 to-[#d4af37]/10 rounded-lg sm:rounded-xl border border-[#d4af37]/40 flex-shrink-0 group-hover/item:bg-gradient-to-br group-hover/item:from-[#d4af37]/30 group-hover/item:to-[#d4af37]/20 group-hover/item:border-[#d4af37]/60 transition-all duration-300 shadow-lg shadow-[#d4af37]/10">
<MapPin className="w-5 h-5 sm:w-6 sm:h-6 text-[#d4af37] drop-shadow-lg" />
</div>
<div>
<h3 className="text-sm sm:text-base font-medium text-[#d4af37] mb-1 sm:mb-2 tracking-wide">Location</h3>
<p className="text-gray-300 font-light text-xs sm:text-sm leading-relaxed whitespace-pre-line">
{displayAddress}
</p>
</div>
</div>
</div>
{}
{pageContent?.map_url && (
<div className="mt-6 sm:mt-7 md:mt-8 pt-6 sm:pt-7 md:pt-8 border-t border-[#d4af37]/30">
<h3 className="text-sm sm:text-base font-medium text-[#d4af37] mb-3 sm:mb-4 tracking-wide">
Find Us
</h3>
<div className="relative rounded-lg overflow-hidden border-2 border-[#d4af37]/30 shadow-lg shadow-[#d4af37]/10 group hover:border-[#d4af37]/50 transition-all duration-300">
<iframe
src={pageContent.map_url}
width="100%"
height="200"
style={{ border: 0 }}
allowFullScreen
loading="lazy"
referrerPolicy="no-referrer-when-downgrade"
className="w-full h-40 sm:h-44 md:h-48 rounded-lg"
title="Hotel Location"
/>
</div>
</div>
)}
<div className="mt-5 sm:mt-6 md:mt-7 pt-5 sm:pt-6 md:pt-7 border-t border-[#d4af37]/30">
<p className="text-gray-400 font-light text-xs sm:text-sm leading-relaxed tracking-wide">
{pageContent?.content || "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>
</div>
</div>
</div>
</div>
{}
<div className="lg:col-span-8">
<div className="bg-gradient-to-br from-[#1a1a1a] via-[#0f0f0f] to-[#0a0a0a]
rounded-xl sm:rounded-2xl border-2 border-[#d4af37]/30 p-5 sm:p-6 md:p-8 lg:p-10
shadow-2xl shadow-[#d4af37]/10 backdrop-blur-xl
relative overflow-hidden">
{}
<div className="absolute inset-0 opacity-5">
<div className="absolute top-0 right-0 w-48 sm:w-64 md:w-96 h-48 sm:h-64 md:h-96 bg-[#d4af37] rounded-full blur-3xl"></div>
</div>
<div className="relative z-10">
<div className="flex items-center gap-2 sm:gap-3 mb-6 sm:mb-7 md:mb-8">
<div className="w-0.5 sm:w-1 h-6 sm:h-8 bg-gradient-to-b from-[#d4af37] to-[#c9a227] rounded-full"></div>
<h2 className="text-xl sm:text-2xl md:text-3xl font-serif font-semibold
text-white tracking-tight">
Send Us a Message
</h2>
</div>
<form onSubmit={handleSubmit} className="space-y-5 sm:space-y-6 md:space-y-7 relative">
{/* Honeypot field - hidden from users */}
<HoneypotField value={honeypotValue} onChange={setHoneypotValue} />
{rateLimitInfo && !rateLimitInfo.allowed && (
<div className="bg-yellow-900/50 backdrop-blur-sm border border-yellow-500/50 text-yellow-200 px-4 py-3 rounded-lg text-sm font-light mb-4">
Too many contact form submissions. Please try again later.
</div>
)}
{}
<div>
<label htmlFor="name" className="block text-xs sm:text-sm font-medium text-gray-300 mb-2 sm:mb-3 tracking-wide">
<span className="flex items-center gap-1.5 sm:gap-2">
<User className="w-3.5 h-3.5 sm:w-4 sm:h-4 text-[#d4af37] drop-shadow-lg" />
Full Name <span className="text-[#d4af37] font-semibold">*</span>
</span>
</label>
<input
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleChange}
className={`w-full px-4 sm:px-5 py-3 sm:py-4 bg-gradient-to-br from-[#0f0f0f] to-[#0a0a0a] border-2 rounded-lg
text-white text-sm sm:text-base placeholder-gray-500/60
focus:outline-none focus:ring-2 focus:ring-[#d4af37]/50 focus:border-[#d4af37] focus:shadow-lg focus:shadow-[#d4af37]/20
transition-all duration-300 hover:border-[#d4af37]/40
${errors.name ? 'border-red-500/50 focus:border-red-500 focus:ring-red-500/50' : 'border-[#d4af37]/30'}`}
placeholder="Enter your full name"
/>
{errors.name && (
<p className="mt-1.5 sm:mt-2 text-xs text-red-400 font-light">{errors.name}</p>
)}
</div>
{}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-5 md:gap-6 lg:gap-7">
{}
<div>
<label htmlFor="email" className="block text-xs sm:text-sm font-medium text-gray-300 mb-2 sm:mb-3 tracking-wide">
<span className="flex items-center gap-1.5 sm:gap-2">
<Mail className="w-3.5 h-3.5 sm:w-4 sm:h-4 text-[#d4af37] drop-shadow-lg" />
Email <span className="text-[#d4af37] font-semibold">*</span>
</span>
</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
className={`w-full px-4 sm:px-5 py-3 sm:py-4 bg-gradient-to-br from-[#0f0f0f] to-[#0a0a0a] border-2 rounded-lg
text-white text-sm sm:text-base placeholder-gray-500/60
focus:outline-none focus:ring-2 focus:ring-[#d4af37]/50 focus:border-[#d4af37] focus:shadow-lg focus:shadow-[#d4af37]/20
transition-all duration-300 hover:border-[#d4af37]/40
${errors.email ? 'border-red-500/50 focus:border-red-500 focus:ring-red-500/50' : 'border-[#d4af37]/30'}`}
placeholder="your.email@example.com"
/>
{errors.email && (
<p className="mt-1.5 sm:mt-2 text-xs text-red-400 font-light">{errors.email}</p>
)}
</div>
{}
<div>
<label htmlFor="phone" className="block text-xs sm:text-sm font-medium text-gray-300 mb-2 sm:mb-3 tracking-wide">
<span className="flex items-center gap-1.5 sm:gap-2">
<Phone className="w-3.5 h-3.5 sm:w-4 sm:h-4 text-[#d4af37] drop-shadow-lg" />
Phone <span className="text-gray-500 text-xs">(Optional)</span>
</span>
</label>
<input
type="tel"
id="phone"
name="phone"
value={formData.phone}
onChange={handleChange}
className="w-full px-4 sm:px-5 py-3 sm:py-4 bg-gradient-to-br from-[#0f0f0f] to-[#0a0a0a] border-2 border-[#d4af37]/30 rounded-lg
text-white text-sm sm:text-base placeholder-gray-500/60
focus:outline-none focus:ring-2 focus:ring-[#d4af37]/50 focus:border-[#d4af37] focus:shadow-lg focus:shadow-[#d4af37]/20
transition-all duration-300 hover:border-[#d4af37]/40"
placeholder="+1 (555) 123-4567"
/>
</div>
</div>
{}
<div>
<label htmlFor="subject" className="block text-xs sm:text-sm font-medium text-gray-300 mb-2 sm:mb-3 tracking-wide">
<span className="flex items-center gap-1.5 sm:gap-2">
<MessageSquare className="w-3.5 h-3.5 sm:w-4 sm:h-4 text-[#d4af37] drop-shadow-lg" />
Subject <span className="text-[#d4af37] font-semibold">*</span>
</span>
</label>
<input
type="text"
id="subject"
name="subject"
value={formData.subject}
onChange={handleChange}
className={`w-full px-4 sm:px-5 py-3 sm:py-4 bg-gradient-to-br from-[#0f0f0f] to-[#0a0a0a] border-2 rounded-lg
text-white text-sm sm:text-base placeholder-gray-500/60
focus:outline-none focus:ring-2 focus:ring-[#d4af37]/50 focus:border-[#d4af37] focus:shadow-lg focus:shadow-[#d4af37]/20
transition-all duration-300 hover:border-[#d4af37]/40
${errors.subject ? 'border-red-500/50 focus:border-red-500 focus:ring-red-500/50' : 'border-[#d4af37]/30'}`}
placeholder="What is this regarding?"
/>
{errors.subject && (
<p className="mt-1.5 sm:mt-2 text-xs text-red-400 font-light">{errors.subject}</p>
)}
</div>
{}
<div>
<label htmlFor="message" className="block text-xs sm:text-sm font-medium text-gray-300 mb-2 sm:mb-3 tracking-wide">
<span className="flex items-center gap-1.5 sm:gap-2">
<MessageSquare className="w-3.5 h-3.5 sm:w-4 sm:h-4 text-[#d4af37] drop-shadow-lg" />
Message <span className="text-[#d4af37] font-semibold">*</span>
</span>
</label>
<textarea
id="message"
name="message"
value={formData.message}
onChange={handleChange}
rows={6}
className={`w-full px-4 sm:px-5 py-3 sm:py-4 bg-gradient-to-br from-[#0f0f0f] to-[#0a0a0a] border-2 rounded-lg
text-white text-sm sm:text-base placeholder-gray-500/60 resize-none
focus:outline-none focus:ring-2 focus:ring-[#d4af37]/50 focus:border-[#d4af37] focus:shadow-lg focus:shadow-[#d4af37]/20
transition-all duration-300 hover:border-[#d4af37]/40
${errors.message ? 'border-red-500/50 focus:border-red-500 focus:ring-red-500/50' : 'border-[#d4af37]/30'}`}
placeholder="Tell us more about your inquiry..."
/>
{errors.message && (
<p className="mt-1.5 sm:mt-2 text-xs text-red-400 font-light">{errors.message}</p>
)}
</div>
{}
<div className="pt-2 sm:pt-3">
<Recaptcha
onChange={(token) => setRecaptchaToken(token)}
onError={(error) => {
console.error('reCAPTCHA error:', error);
setRecaptchaToken(null);
}}
theme="dark"
size="normal"
className="flex justify-center"
/>
</div>
{}
<div className="pt-2 sm:pt-3 md:pt-4">
<button
type="submit"
disabled={loading}
className="group w-full sm:w-auto inline-flex items-center justify-center gap-2 sm:gap-3
bg-gradient-to-r from-[#d4af37] via-[#f5d76e] to-[#d4af37]
text-[#0f0f0f] font-semibold
active:scale-[0.98]
disabled:opacity-50 disabled:cursor-not-allowed
transition-all duration-500
tracking-wide text-sm sm:text-base
px-6 sm:px-8 md:px-10 py-3 sm:py-3.5 md:py-4 rounded-lg
shadow-2xl shadow-[#d4af37]/40 hover:shadow-[#d4af37]/60 hover:scale-[1.02]
relative overflow-hidden
touch-manipulation min-h-[44px] sm:min-h-[48px] md:min-h-[52px]"
>
<div className="absolute inset-0 bg-gradient-to-r from-white/0 via-white/30 to-white/0 translate-x-[-100%] group-hover:translate-x-[100%] transition-transform duration-1000"></div>
{loading ? (
<>
<div className="w-4 h-4 sm:w-5 sm:h-5 border-2 border-[#0f0f0f] border-t-transparent rounded-full animate-spin relative z-10" />
<span className="relative z-10">Sending...</span>
</>
) : (
<>
<Send className="w-4 h-4 sm:w-5 sm:h-5 relative z-10 group-hover:translate-x-1 transition-transform duration-300" />
<span className="relative z-10">Send Message</span>
</>
)}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{}
<ChatWidget />
</div>
);
};
export default ContactPage;

View File

@@ -1,179 +0,0 @@
import React, { useState, useEffect } from 'react';
import { HelpCircle, ArrowLeft } from 'lucide-react';
import { Link } from 'react-router-dom';
import { pageContentService } from '../services/api';
import type { PageContent } from '../services/api/pageContentService';
import { useCompanySettings } from '../contexts/CompanySettingsContext';
import Loading from '../components/common/Loading';
import { createSanitizedHtml } from '../utils/htmlSanitizer';
const FAQPage: React.FC = () => {
const { settings } = useCompanySettings();
const [pageContent, setPageContent] = useState<PageContent | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchPageContent = async () => {
try {
setLoading(true);
const response = await pageContentService.getFAQContent();
if (response.status === 'success' && response.data?.page_content) {
const content = response.data.page_content;
if (content.content) {
const tempDiv = document.createElement('div');
tempDiv.innerHTML = content.content;
const allElements = tempDiv.querySelectorAll('*');
allElements.forEach((el) => {
const htmlEl = el as HTMLElement;
const tagName = htmlEl.tagName.toLowerCase();
const currentColor = htmlEl.style.color;
if (!currentColor || currentColor === 'black' || currentColor === '#000' || currentColor === '#000000') {
if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName)) {
htmlEl.style.color = '#ffffff';
} else if (['strong', 'b'].includes(tagName)) {
htmlEl.style.color = '#d4af37';
} else if (tagName === 'a') {
htmlEl.style.color = '#d4af37';
} else {
htmlEl.style.color = '#d1d5db';
}
}
});
content.content = tempDiv.innerHTML;
}
setPageContent(content);
if (content.meta_title) {
document.title = content.meta_title;
}
if (content.meta_description) {
let metaDescription = document.querySelector('meta[name="description"]');
if (!metaDescription) {
metaDescription = document.createElement('meta');
metaDescription.setAttribute('name', 'description');
document.head.appendChild(metaDescription);
}
metaDescription.setAttribute('content', content.meta_description);
}
}
} catch (err: any) {
console.error('Error fetching page content:', err);
// If page is disabled (404), set pageContent to null to show disabled message
if (err.response?.status === 404) {
setPageContent(null);
}
} finally {
setLoading(false);
}
};
fetchPageContent();
}, []);
if (loading) {
return <Loading />;
}
if (!pageContent) {
return (
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-screen relative -mt-6 -mb-6 flex items-center justify-center"
style={{
marginLeft: 'calc(50% - 50vw)',
marginRight: 'calc(50% - 50vw)',
width: '100vw',
paddingTop: '2rem',
paddingBottom: '2rem',
zIndex: 1
}}
>
<div className="text-center">
<HelpCircle className="w-16 h-16 text-[#d4af37]/50 mx-auto mb-4" />
<h1 className="text-2xl font-elegant font-bold text-white mb-2">Frequently Asked Questions</h1>
<p className="text-gray-400">This page is currently unavailable.</p>
<Link
to="/"
className="inline-flex items-center gap-2 text-[#d4af37]/80 hover:text-[#d4af37] mt-6 transition-all duration-300"
>
<ArrowLeft className="w-4 h-4" />
<span>Back to Home</span>
</Link>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-screen relative -mt-6 -mb-6"
style={{
marginLeft: 'calc(50% - 50vw)',
marginRight: 'calc(50% - 50vw)',
width: '100vw',
paddingTop: '2rem',
paddingBottom: '2rem',
zIndex: 1
}}
>
<div className="w-full px-3 sm:px-4 md:px-6 lg:px-8 py-8 sm:py-12 max-w-5xl mx-auto">
<Link
to="/"
className="inline-flex items-center gap-2 text-[#d4af37]/80 hover:text-[#d4af37] mb-6 transition-all duration-300 group font-light tracking-wide text-xs sm:text-sm"
>
<ArrowLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" />
<span>Back to Home</span>
</Link>
<div className="mb-8 sm:mb-12 text-center">
<div className="inline-flex items-center justify-center w-16 h-16 sm:w-20 sm:h-20 mb-4 sm:mb-6 bg-gradient-to-br from-[#d4af37]/20 to-[#c9a227]/10 rounded-full border border-[#d4af37]/30">
<HelpCircle className="w-8 h-8 sm:w-10 sm:h-10 text-[#d4af37]" />
</div>
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-elegant font-bold text-white mb-3 sm:mb-4 tracking-tight leading-tight bg-gradient-to-r from-white via-[#d4af37] to-white bg-clip-text text-transparent">
{pageContent.title || 'Frequently Asked Questions'}
</h1>
{pageContent.subtitle && (
<p className="text-base sm:text-lg text-gray-300 font-light tracking-wide">
{pageContent.subtitle}
</p>
)}
</div>
<div className="bg-gradient-to-br from-[#1a1a1a] to-[#0a0a0a] rounded-xl border border-[#d4af37]/20 backdrop-blur-xl shadow-2xl shadow-[#d4af37]/5 p-6 sm:p-8 lg:p-12">
<div
className="prose prose-invert prose-lg max-w-none text-gray-300
prose-headings:text-white prose-headings:font-elegant prose-headings:font-semibold
prose-h2:text-2xl prose-h2:mt-8 prose-h2:mb-4 prose-h2:border-b prose-h2:border-[#d4af37]/20 prose-h2:pb-2
prose-p:text-gray-300 prose-p:font-light prose-p:leading-relaxed prose-p:mb-4
prose-ul:text-gray-300 prose-ul:font-light prose-ul:my-4
prose-li:text-gray-300 prose-li:mb-2 prose-li:ml-4
prose-strong:text-[#d4af37] prose-strong:font-medium
prose-a:text-[#d4af37] prose-a:no-underline hover:prose-a:underline
[&_*]:text-gray-300 [&_h1]:text-white [&_h2]:text-white [&_h3]:text-white [&_h4]:text-white [&_h5]:text-white [&_h6]:text-white
[&_strong]:text-[#d4af37] [&_b]:text-[#d4af37] [&_a]:text-[#d4af37]"
style={{ color: '#d1d5db' }}
dangerouslySetInnerHTML={createSanitizedHtml(
pageContent.content || pageContent.description || '<p style="color: #d1d5db;">No content available.</p>'
)}
/>
</div>
{settings.company_email && (
<div className="mt-8 text-center">
<p className="text-sm text-gray-400 font-light">
Still have questions? Contact us at{' '}
<a href={`mailto:${settings.company_email}`} className="text-[#d4af37] hover:underline">
{settings.company_email}
</a>
</p>
</div>
)}
</div>
</div>
);
};
export default FAQPage;

File diff suppressed because it is too large Load Diff

View File

@@ -1,101 +0,0 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Home, Hotel, AlertCircle } from 'lucide-react';
import { useCompanySettings } from '../contexts/CompanySettingsContext';
const NotFoundPage: React.FC = () => {
const { settings } = useCompanySettings();
return (
<div className="min-h-screen bg-gradient-to-br from-gray-50 via-gray-100 to-gray-50 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8 relative overflow-hidden">
{/* Background Pattern */}
<div
className="absolute inset-0 opacity-5"
style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M9 0h2v20H9zM25 0h2v20h-2zM41 0h2v20h-2zM57 0h2v20h-2zM0 9h20v2H0zM0 25h20v2H0zM0 41h20v2H0zM0 57h20v2H0zM40 9h20v2H40zM40 25h20v2H40zM40 41h20v2H40zM40 57h20v2H40zM9 40h2v20H9zM25 40h2v20h-2zM41 40h2v20h-2zM57 40h2v20h-2z' fill='%23d4af37' opacity='0.05'/%3E%3C/svg%3E")`
}}
/>
<div className="max-w-2xl w-full text-center relative z-10">
{/* Logo */}
<div className="flex justify-center mb-6 sm:mb-8">
{settings.company_logo_url ? (
<img
src={settings.company_logo_url.startsWith('http')
? settings.company_logo_url
: `${import.meta.env.VITE_API_URL || 'http://localhost:8000'}${settings.company_logo_url}`}
alt={settings.company_name || 'Logo'}
className="h-16 sm:h-20 lg:h-24 w-auto max-w-[200px] sm:max-w-[250px] object-contain"
style={{ filter: 'drop-shadow(0 4px 6px rgba(0,0,0,0.1))' }}
/>
) : (
<div className="relative p-4 sm:p-5 bg-gradient-to-br from-[#d4af37] to-[#c9a227] rounded-full shadow-lg shadow-[#d4af37]/30">
<Hotel className="w-12 h-12 sm:w-16 sm:h-16 lg:w-20 lg:h-20 text-[#0f0f0f] relative z-10" />
<div className="absolute inset-0 bg-gradient-to-br from-[#f5d76e] to-[#d4af37] rounded-full blur-xl opacity-50"></div>
</div>
)}
</div>
{/* Tagline */}
{settings.company_tagline && (
<p className="text-xs sm:text-sm text-[#d4af37] uppercase tracking-[2px] sm:tracking-[3px] mb-3 sm:mb-4 font-light">
{settings.company_tagline}
</p>
)}
{/* 404 Number */}
<div className="mb-6 sm:mb-8">
<h1 className="text-8xl sm:text-9xl lg:text-[12rem] font-display font-bold text-transparent bg-clip-text bg-gradient-to-r from-[#d4af37] via-[#f5d76e] to-[#d4af37] leading-none tracking-tight">
404
</h1>
</div>
{/* Error Icon */}
<div className="flex justify-center mb-6 sm:mb-8">
<div className="relative p-4 sm:p-5 bg-red-50 rounded-full border-2 border-red-200">
<AlertCircle className="w-8 h-8 sm:w-10 sm:h-10 text-red-500" />
</div>
</div>
{/* Main Message */}
<div className="mb-6 sm:mb-8">
<h2 className="text-2xl sm:text-3xl lg:text-4xl font-elegant font-semibold text-gray-900 mb-3 sm:mb-4 tracking-tight">
Page Not Found
</h2>
<p className="text-base sm:text-lg text-gray-600 font-light tracking-wide max-w-md mx-auto leading-relaxed">
We're sorry, but the page you're looking for doesn't exist or has been moved.
</p>
</div>
{/* Additional Info */}
<div className="mb-8 sm:mb-10">
<p className="text-sm sm:text-base text-gray-500 font-light">
The page you requested could not be found on our server.
</p>
</div>
{/* Home Button */}
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
<Link
to="/"
className="inline-flex items-center justify-center gap-2 px-8 py-3.5 sm:px-10 sm:py-4 bg-gradient-to-r from-[#d4af37] to-[#c9a227] text-[#0f0f0f] font-semibold text-sm sm:text-base rounded-lg shadow-lg shadow-[#d4af37]/30 hover:from-[#f5d76e] hover:to-[#d4af37] hover:shadow-xl hover:shadow-[#d4af37]/40 transition-all duration-300 ease-out hover:-translate-y-0.5 active:translate-y-0 relative overflow-hidden group"
>
<span className="absolute inset-0 bg-gradient-to-r from-white/0 via-white/20 to-white/0 translate-x-[-100%] group-hover:translate-x-[100%] transition-transform duration-700"></span>
<Home className="w-5 h-5 sm:w-6 sm:h-6 relative z-10" />
<span className="relative z-10 tracking-wide">Back to Homepage</span>
</Link>
</div>
{/* Decorative Elements */}
<div className="mt-12 sm:mt-16 flex justify-center gap-2">
<div className="w-2 h-2 rounded-full bg-[#d4af37]/30"></div>
<div className="w-2 h-2 rounded-full bg-[#d4af37]/50"></div>
<div className="w-2 h-2 rounded-full bg-[#d4af37]/30"></div>
</div>
</div>
</div>
);
};
export default NotFoundPage;

View File

@@ -1,188 +0,0 @@
import React, { useState, useEffect } from 'react';
import { Shield, ArrowLeft } from 'lucide-react';
import { Link } from 'react-router-dom';
import { pageContentService } from '../services/api';
import type { PageContent } from '../services/api/pageContentService';
import { useCompanySettings } from '../contexts/CompanySettingsContext';
import Loading from '../components/common/Loading';
import { createSanitizedHtml } from '../utils/htmlSanitizer';
const PrivacyPolicyPage: React.FC = () => {
const { settings } = useCompanySettings();
const [pageContent, setPageContent] = useState<PageContent | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchPageContent = async () => {
try {
setLoading(true);
const response = await pageContentService.getPrivacyContent();
if (response.status === 'success' && response.data?.page_content) {
const content = response.data.page_content;
// Process HTML content to ensure text is visible
if (content.content) {
// Create a temporary div to parse HTML
const tempDiv = document.createElement('div');
tempDiv.innerHTML = content.content;
// Add color styles to elements that don't have them
const allElements = tempDiv.querySelectorAll('*');
allElements.forEach((el) => {
const htmlEl = el as HTMLElement;
const tagName = htmlEl.tagName.toLowerCase();
const currentColor = htmlEl.style.color;
// Only add color if not already set
if (!currentColor || currentColor === 'black' || currentColor === '#000' || currentColor === '#000000') {
if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName)) {
htmlEl.style.color = '#ffffff';
} else if (['strong', 'b'].includes(tagName)) {
htmlEl.style.color = '#d4af37';
} else if (tagName === 'a') {
htmlEl.style.color = '#d4af37';
} else {
htmlEl.style.color = '#d1d5db';
}
}
});
// Update content with processed HTML
content.content = tempDiv.innerHTML;
}
setPageContent(content);
if (content.meta_title) {
document.title = content.meta_title;
}
if (content.meta_description) {
let metaDescription = document.querySelector('meta[name="description"]');
if (!metaDescription) {
metaDescription = document.createElement('meta');
metaDescription.setAttribute('name', 'description');
document.head.appendChild(metaDescription);
}
metaDescription.setAttribute('content', content.meta_description);
}
}
} catch (err: any) {
console.error('Error fetching page content:', err);
// If page is disabled (404), set pageContent to null to show disabled message
if (err.response?.status === 404) {
setPageContent(null);
}
} finally {
setLoading(false);
}
};
fetchPageContent();
}, []);
if (loading) {
return <Loading />;
}
if (!pageContent) {
return (
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-screen relative -mt-6 -mb-6 flex items-center justify-center"
style={{
marginLeft: 'calc(50% - 50vw)',
marginRight: 'calc(50% - 50vw)',
width: '100vw',
paddingTop: '2rem',
paddingBottom: '2rem',
zIndex: 1
}}
>
<div className="text-center">
<Shield className="w-16 h-16 text-[#d4af37]/50 mx-auto mb-4" />
<h1 className="text-2xl font-elegant font-bold text-white mb-2">Privacy Policy</h1>
<p className="text-gray-400">This page is currently unavailable.</p>
<Link
to="/"
className="inline-flex items-center gap-2 text-[#d4af37]/80 hover:text-[#d4af37] mt-6 transition-all duration-300"
>
<ArrowLeft className="w-4 h-4" />
<span>Back to Home</span>
</Link>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-screen relative -mt-6 -mb-6"
style={{
marginLeft: 'calc(50% - 50vw)',
marginRight: 'calc(50% - 50vw)',
width: '100vw',
paddingTop: '2rem',
paddingBottom: '2rem',
zIndex: 1
}}
>
<div className="w-full px-3 sm:px-4 md:px-6 lg:px-8 py-8 sm:py-12 max-w-5xl mx-auto">
{/* Back Link */}
<Link
to="/"
className="inline-flex items-center gap-2 text-[#d4af37]/80 hover:text-[#d4af37] mb-6 transition-all duration-300 group font-light tracking-wide text-xs sm:text-sm"
>
<ArrowLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" />
<span>Back to Home</span>
</Link>
{/* Header */}
<div className="mb-8 sm:mb-12 text-center">
<div className="inline-flex items-center justify-center w-16 h-16 sm:w-20 sm:h-20 mb-4 sm:mb-6 bg-gradient-to-br from-[#d4af37]/20 to-[#c9a227]/10 rounded-full border border-[#d4af37]/30">
<Shield className="w-8 h-8 sm:w-10 sm:h-10 text-[#d4af37]" />
</div>
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-elegant font-bold text-white mb-3 sm:mb-4 tracking-tight leading-tight bg-gradient-to-r from-white via-[#d4af37] to-white bg-clip-text text-transparent">
{pageContent.title || 'Privacy Policy'}
</h1>
{pageContent.subtitle && (
<p className="text-base sm:text-lg text-gray-300 font-light tracking-wide">
{pageContent.subtitle}
</p>
)}
</div>
{/* Content */}
<div className="bg-gradient-to-br from-[#1a1a1a] to-[#0a0a0a] rounded-xl border border-[#d4af37]/20 backdrop-blur-xl shadow-2xl shadow-[#d4af37]/5 p-6 sm:p-8 lg:p-12">
<div
className="prose prose-invert prose-lg max-w-none text-gray-300
prose-headings:text-white prose-headings:font-elegant prose-headings:font-semibold
prose-h2:text-2xl prose-h2:mt-8 prose-h2:mb-4 prose-h2:border-b prose-h2:border-[#d4af37]/20 prose-h2:pb-2
prose-p:text-gray-300 prose-p:font-light prose-p:leading-relaxed prose-p:mb-4
prose-ul:text-gray-300 prose-ul:font-light prose-ul:my-4
prose-li:text-gray-300 prose-li:mb-2 prose-li:ml-4
prose-strong:text-[#d4af37] prose-strong:font-medium
prose-a:text-[#d4af37] prose-a:no-underline hover:prose-a:underline
[&_*]:text-gray-300 [&_h1]:text-white [&_h2]:text-white [&_h3]:text-white [&_h4]:text-white [&_h5]:text-white [&_h6]:text-white
[&_strong]:text-[#d4af37] [&_b]:text-[#d4af37] [&_a]:text-[#d4af37]"
style={{ color: '#d1d5db' }}
dangerouslySetInnerHTML={createSanitizedHtml(
pageContent.content || pageContent.description || '<p style="color: #d1d5db;">No content available.</p>'
)}
/>
</div>
{/* Footer Note */}
{settings.company_email && (
<div className="mt-8 text-center">
<p className="text-sm text-gray-400 font-light">
For questions about this policy, contact us at{' '}
<a href={`mailto:${settings.company_email}`} className="text-[#d4af37] hover:underline">
{settings.company_email}
</a>
</p>
</div>
)}
</div>
</div>
);
};
export default PrivacyPolicyPage;

View File

@@ -1,188 +0,0 @@
import React, { useState, useEffect } from 'react';
import { RefreshCw, ArrowLeft } from 'lucide-react';
import { Link } from 'react-router-dom';
import { pageContentService } from '../services/api';
import type { PageContent } from '../services/api/pageContentService';
import { useCompanySettings } from '../contexts/CompanySettingsContext';
import Loading from '../components/common/Loading';
import { createSanitizedHtml } from '../utils/htmlSanitizer';
const RefundsPolicyPage: React.FC = () => {
const { settings } = useCompanySettings();
const [pageContent, setPageContent] = useState<PageContent | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchPageContent = async () => {
try {
setLoading(true);
const response = await pageContentService.getRefundsContent();
if (response.status === 'success' && response.data?.page_content) {
const content = response.data.page_content;
// Process HTML content to ensure text is visible
if (content.content) {
// Create a temporary div to parse HTML
const tempDiv = document.createElement('div');
tempDiv.innerHTML = content.content;
// Add color styles to elements that don't have them
const allElements = tempDiv.querySelectorAll('*');
allElements.forEach((el) => {
const htmlEl = el as HTMLElement;
const tagName = htmlEl.tagName.toLowerCase();
const currentColor = htmlEl.style.color;
// Only add color if not already set
if (!currentColor || currentColor === 'black' || currentColor === '#000' || currentColor === '#000000') {
if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName)) {
htmlEl.style.color = '#ffffff';
} else if (['strong', 'b'].includes(tagName)) {
htmlEl.style.color = '#d4af37';
} else if (tagName === 'a') {
htmlEl.style.color = '#d4af37';
} else {
htmlEl.style.color = '#d1d5db';
}
}
});
// Update content with processed HTML
content.content = tempDiv.innerHTML;
}
setPageContent(content);
if (content.meta_title) {
document.title = content.meta_title;
}
if (content.meta_description) {
let metaDescription = document.querySelector('meta[name="description"]');
if (!metaDescription) {
metaDescription = document.createElement('meta');
metaDescription.setAttribute('name', 'description');
document.head.appendChild(metaDescription);
}
metaDescription.setAttribute('content', content.meta_description);
}
}
} catch (err: any) {
console.error('Error fetching page content:', err);
// If page is disabled (404), set pageContent to null to show disabled message
if (err.response?.status === 404) {
setPageContent(null);
}
} finally {
setLoading(false);
}
};
fetchPageContent();
}, []);
if (loading) {
return <Loading />;
}
if (!pageContent) {
return (
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-screen relative -mt-6 -mb-6 flex items-center justify-center"
style={{
marginLeft: 'calc(50% - 50vw)',
marginRight: 'calc(50% - 50vw)',
width: '100vw',
paddingTop: '2rem',
paddingBottom: '2rem',
zIndex: 1
}}
>
<div className="text-center">
<RefreshCw className="w-16 h-16 text-[#d4af37]/50 mx-auto mb-4" />
<h1 className="text-2xl font-elegant font-bold text-white mb-2">Refunds Policy</h1>
<p className="text-gray-400">This page is currently unavailable.</p>
<Link
to="/"
className="inline-flex items-center gap-2 text-[#d4af37]/80 hover:text-[#d4af37] mt-6 transition-all duration-300"
>
<ArrowLeft className="w-4 h-4" />
<span>Back to Home</span>
</Link>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-screen relative -mt-6 -mb-6"
style={{
marginLeft: 'calc(50% - 50vw)',
marginRight: 'calc(50% - 50vw)',
width: '100vw',
paddingTop: '2rem',
paddingBottom: '2rem',
zIndex: 1
}}
>
<div className="w-full px-3 sm:px-4 md:px-6 lg:px-8 py-8 sm:py-12 max-w-5xl mx-auto">
{/* Back Link */}
<Link
to="/"
className="inline-flex items-center gap-2 text-[#d4af37]/80 hover:text-[#d4af37] mb-6 transition-all duration-300 group font-light tracking-wide text-xs sm:text-sm"
>
<ArrowLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" />
<span>Back to Home</span>
</Link>
{/* Header */}
<div className="mb-8 sm:mb-12 text-center">
<div className="inline-flex items-center justify-center w-16 h-16 sm:w-20 sm:h-20 mb-4 sm:mb-6 bg-gradient-to-br from-[#d4af37]/20 to-[#c9a227]/10 rounded-full border border-[#d4af37]/30">
<RefreshCw className="w-8 h-8 sm:w-10 sm:h-10 text-[#d4af37]" />
</div>
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-elegant font-bold text-white mb-3 sm:mb-4 tracking-tight leading-tight bg-gradient-to-r from-white via-[#d4af37] to-white bg-clip-text text-transparent">
{pageContent.title || 'Refunds Policy'}
</h1>
{pageContent.subtitle && (
<p className="text-base sm:text-lg text-gray-300 font-light tracking-wide">
{pageContent.subtitle}
</p>
)}
</div>
{/* Content */}
<div className="bg-gradient-to-br from-[#1a1a1a] to-[#0a0a0a] rounded-xl border border-[#d4af37]/20 backdrop-blur-xl shadow-2xl shadow-[#d4af37]/5 p-6 sm:p-8 lg:p-12">
<div
className="prose prose-invert prose-lg max-w-none text-gray-300
prose-headings:text-white prose-headings:font-elegant prose-headings:font-semibold
prose-h2:text-2xl prose-h2:mt-8 prose-h2:mb-4 prose-h2:border-b prose-h2:border-[#d4af37]/20 prose-h2:pb-2
prose-p:text-gray-300 prose-p:font-light prose-p:leading-relaxed prose-p:mb-4
prose-ul:text-gray-300 prose-ul:font-light prose-ul:my-4
prose-li:text-gray-300 prose-li:mb-2 prose-li:ml-4
prose-strong:text-[#d4af37] prose-strong:font-medium
prose-a:text-[#d4af37] prose-a:no-underline hover:prose-a:underline
[&_*]:text-gray-300 [&_h1]:text-white [&_h2]:text-white [&_h3]:text-white [&_h4]:text-white [&_h5]:text-white [&_h6]:text-white
[&_strong]:text-[#d4af37] [&_b]:text-[#d4af37] [&_a]:text-[#d4af37]"
style={{ color: '#d1d5db' }}
dangerouslySetInnerHTML={createSanitizedHtml(
pageContent.content || pageContent.description || '<p style="color: #d1d5db;">No content available.</p>'
)}
/>
</div>
{/* Footer Note */}
{settings.company_email && (
<div className="mt-8 text-center">
<p className="text-sm text-gray-400 font-light">
For refund inquiries, contact us at{' '}
<a href={`mailto:${settings.company_email}`} className="text-[#d4af37] hover:underline">
{settings.company_email}
</a>
</p>
</div>
)}
</div>
</div>
);
};
export default RefundsPolicyPage;

View File

@@ -1,9 +1,9 @@
import React from 'react';
import { Outlet } from 'react-router-dom';
import { SidebarStaff } from '../components/layout';
import StaffChatNotification from '../components/chat/StaffChatNotification';
import { ChatNotificationProvider } from '../contexts/ChatNotificationContext';
import { useResponsive } from '../hooks';
import SidebarStaff from '../shared/components/SidebarStaff';
import StaffChatNotification from '../features/notifications/components/StaffChatNotification';
import { ChatNotificationProvider } from '../features/notifications/contexts/ChatNotificationContext';
import { useResponsive } from '../shared/hooks/useResponsive';
const StaffLayout: React.FC = () => {
const { isMobile } = useResponsive();

View File

@@ -1,188 +0,0 @@
import React, { useState, useEffect } from 'react';
import { Scale, ArrowLeft } from 'lucide-react';
import { Link } from 'react-router-dom';
import { pageContentService } from '../services/api';
import type { PageContent } from '../services/api/pageContentService';
import { useCompanySettings } from '../contexts/CompanySettingsContext';
import Loading from '../components/common/Loading';
import { createSanitizedHtml } from '../utils/htmlSanitizer';
const TermsPage: React.FC = () => {
const { settings } = useCompanySettings();
const [pageContent, setPageContent] = useState<PageContent | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchPageContent = async () => {
try {
setLoading(true);
const response = await pageContentService.getTermsContent();
if (response.status === 'success' && response.data?.page_content) {
const content = response.data.page_content;
// Process HTML content to ensure text is visible
if (content.content) {
// Create a temporary div to parse HTML
const tempDiv = document.createElement('div');
tempDiv.innerHTML = content.content;
// Add color styles to elements that don't have them
const allElements = tempDiv.querySelectorAll('*');
allElements.forEach((el) => {
const htmlEl = el as HTMLElement;
const tagName = htmlEl.tagName.toLowerCase();
const currentColor = htmlEl.style.color;
// Only add color if not already set
if (!currentColor || currentColor === 'black' || currentColor === '#000' || currentColor === '#000000') {
if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName)) {
htmlEl.style.color = '#ffffff';
} else if (['strong', 'b'].includes(tagName)) {
htmlEl.style.color = '#d4af37';
} else if (tagName === 'a') {
htmlEl.style.color = '#d4af37';
} else {
htmlEl.style.color = '#d1d5db';
}
}
});
// Update content with processed HTML
content.content = tempDiv.innerHTML;
}
setPageContent(content);
if (content.meta_title) {
document.title = content.meta_title;
}
if (content.meta_description) {
let metaDescription = document.querySelector('meta[name="description"]');
if (!metaDescription) {
metaDescription = document.createElement('meta');
metaDescription.setAttribute('name', 'description');
document.head.appendChild(metaDescription);
}
metaDescription.setAttribute('content', content.meta_description);
}
}
} catch (err: any) {
console.error('Error fetching page content:', err);
// If page is disabled (404), set pageContent to null to show disabled message
if (err.response?.status === 404) {
setPageContent(null);
}
} finally {
setLoading(false);
}
};
fetchPageContent();
}, []);
if (loading) {
return <Loading />;
}
if (!pageContent) {
return (
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-screen relative -mt-6 -mb-6 flex items-center justify-center"
style={{
marginLeft: 'calc(50% - 50vw)',
marginRight: 'calc(50% - 50vw)',
width: '100vw',
paddingTop: '2rem',
paddingBottom: '2rem',
zIndex: 1
}}
>
<div className="text-center">
<Scale className="w-16 h-16 text-[#d4af37]/50 mx-auto mb-4" />
<h1 className="text-2xl font-elegant font-bold text-white mb-2">Terms & Conditions</h1>
<p className="text-gray-400">This page is currently unavailable.</p>
<Link
to="/"
className="inline-flex items-center gap-2 text-[#d4af37]/80 hover:text-[#d4af37] mt-6 transition-all duration-300"
>
<ArrowLeft className="w-4 h-4" />
<span>Back to Home</span>
</Link>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-screen relative -mt-6 -mb-6"
style={{
marginLeft: 'calc(50% - 50vw)',
marginRight: 'calc(50% - 50vw)',
width: '100vw',
paddingTop: '2rem',
paddingBottom: '2rem',
zIndex: 1
}}
>
<div className="w-full px-3 sm:px-4 md:px-6 lg:px-8 py-8 sm:py-12 max-w-5xl mx-auto">
{/* Back Link */}
<Link
to="/"
className="inline-flex items-center gap-2 text-[#d4af37]/80 hover:text-[#d4af37] mb-6 transition-all duration-300 group font-light tracking-wide text-xs sm:text-sm"
>
<ArrowLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" />
<span>Back to Home</span>
</Link>
{/* Header */}
<div className="mb-8 sm:mb-12 text-center">
<div className="inline-flex items-center justify-center w-16 h-16 sm:w-20 sm:h-20 mb-4 sm:mb-6 bg-gradient-to-br from-[#d4af37]/20 to-[#c9a227]/10 rounded-full border border-[#d4af37]/30">
<Scale className="w-8 h-8 sm:w-10 sm:h-10 text-[#d4af37]" />
</div>
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-elegant font-bold text-white mb-3 sm:mb-4 tracking-tight leading-tight bg-gradient-to-r from-white via-[#d4af37] to-white bg-clip-text text-transparent">
{pageContent.title || 'Terms & Conditions'}
</h1>
{pageContent.subtitle && (
<p className="text-base sm:text-lg text-gray-300 font-light tracking-wide">
{pageContent.subtitle}
</p>
)}
</div>
{/* Content */}
<div className="bg-gradient-to-br from-[#1a1a1a] to-[#0a0a0a] rounded-xl border border-[#d4af37]/20 backdrop-blur-xl shadow-2xl shadow-[#d4af37]/5 p-6 sm:p-8 lg:p-12">
<div
className="prose prose-invert prose-lg max-w-none text-gray-300
prose-headings:text-white prose-headings:font-elegant prose-headings:font-semibold
prose-h2:text-2xl prose-h2:mt-8 prose-h2:mb-4 prose-h2:border-b prose-h2:border-[#d4af37]/20 prose-h2:pb-2
prose-p:text-gray-300 prose-p:font-light prose-p:leading-relaxed prose-p:mb-4
prose-ul:text-gray-300 prose-ul:font-light prose-ul:my-4
prose-li:text-gray-300 prose-li:mb-2 prose-li:ml-4
prose-strong:text-[#d4af37] prose-strong:font-medium
prose-a:text-[#d4af37] prose-a:no-underline hover:prose-a:underline
[&_*]:text-gray-300 [&_h1]:text-white [&_h2]:text-white [&_h3]:text-white [&_h4]:text-white [&_h5]:text-white [&_h6]:text-white
[&_strong]:text-[#d4af37] [&_b]:text-[#d4af37] [&_a]:text-[#d4af37]"
style={{ color: '#d1d5db' }}
dangerouslySetInnerHTML={createSanitizedHtml(
pageContent.content || pageContent.description || '<p style="color: #d1d5db;">No content available.</p>'
)}
/>
</div>
{/* Footer Note */}
{settings.company_email && (
<div className="mt-8 text-center">
<p className="text-sm text-gray-400 font-light">
For questions about these terms, contact us at{' '}
<a href={`mailto:${settings.company_email}`} className="text-[#d4af37] hover:underline">
{settings.company_email}
</a>
</p>
</div>
)}
</div>
</div>
);
};
export default TermsPage;

View File

@@ -1,9 +1,9 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor } from '../../test/utils/test-utils';
import HomePage from '../HomePage';
import HomePage from '../../features/content/pages/HomePage';
// Mock the components that might cause issues
vi.mock('../../components/rooms/BannerCarousel', () => ({
vi.mock('../../features/rooms/components/BannerCarousel', () => ({
default: ({ children, banners }: any) => (
<div data-testid="banner-carousel">
{banners.map((banner: any) => (
@@ -16,7 +16,7 @@ vi.mock('../../components/rooms/BannerCarousel', () => ({
),
}));
vi.mock('../../components/rooms/SearchRoomForm', () => ({
vi.mock('../../features/rooms/components/SearchRoomForm', () => ({
default: ({ className }: any) => (
<div data-testid="search-room-form" className={className}>
Search Form
@@ -24,7 +24,7 @@ vi.mock('../../components/rooms/SearchRoomForm', () => ({
),
}));
vi.mock('../../components/rooms/RoomCarousel', () => ({
vi.mock('../../features/rooms/components/RoomCarousel', () => ({
default: ({ rooms }: any) => (
<div data-testid="room-carousel">
{rooms.map((room: any) => (
@@ -36,11 +36,11 @@ vi.mock('../../components/rooms/RoomCarousel', () => ({
),
}));
vi.mock('../../components/rooms/BannerSkeleton', () => ({
vi.mock('../../features/rooms/components/BannerSkeleton', () => ({
default: () => <div data-testid="banner-skeleton">Loading banners...</div>,
}));
vi.mock('../../components/rooms/RoomCardSkeleton', () => ({
vi.mock('../../features/rooms/components/RoomCardSkeleton', () => ({
default: () => <div data-testid="room-card-skeleton">Loading room...</div>,
}));

View File

@@ -29,14 +29,17 @@ import {
Award
} from 'lucide-react';
import { toast } from 'react-toastify';
import { Loading, EmptyState, ExportButton } from '../../components/common';
import Pagination from '../../components/common/Pagination';
import CurrencyIcon from '../../components/common/CurrencyIcon';
import { useAsync } from '../../hooks/useAsync';
import { reportService, ReportData, reviewService, Review } from '../../services/api';
import { auditService, AuditLog, AuditLogFilters } from '../../services/api/auditService';
import { formatDate } from '../../utils/format';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import Loading from '../../shared/components/Loading';
import EmptyState from '../../shared/components/EmptyState';
import ExportButton from '../../shared/components/ExportButton';
import Pagination from '../../shared/components/Pagination';
import CurrencyIcon from '../../shared/components/CurrencyIcon';
import { useAsync } from '../../shared/hooks/useAsync';
import reportService, { ReportData } from '../../features/analytics/services/reportService';
import reviewService, { Review } from '../../features/reviews/services/reviewService';
import { auditService, AuditLog, AuditLogFilters } from '../../features/analytics/services/auditService';
import { formatDate } from '../../shared/utils/format';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import analyticsService, {
ComprehensiveAnalyticsData,
RevPARData,
@@ -53,10 +56,10 @@ import analyticsService, {
ProfitLossData,
PaymentMethodAnalyticsData,
RefundAnalysisData,
} from '../../services/api/analyticsService';
import { SimpleBarChart, SimpleLineChart, SimplePieChart, KPICard } from '../../components/analytics/SimpleChart';
import { exportData } from '../../utils/exportUtils';
import CustomReportBuilder from '../../components/analytics/CustomReportBuilder';
} from '../../features/analytics/services/analyticsService';
import { SimpleBarChart, SimpleLineChart, SimplePieChart, KPICard } from '../../features/analytics/components/SimpleChart';
import { exportData } from '../../shared/utils/exportUtils';
import CustomReportBuilder from '../../features/analytics/components/CustomReportBuilder';
type AnalyticsTab = 'overview' | 'reports' | 'revenue' | 'operational' | 'guest' | 'financial' | 'audit-logs' | 'reviews';

View File

@@ -7,14 +7,18 @@ import {
DollarSign,
AlertCircle
} from 'lucide-react';
import { reportService, ReportData, paymentService, invoiceService } from '../../services/api';
import type { Payment } from '../../services/api/paymentService';
import type { Invoice } from '../../services/api/invoiceService';
import reportService, { ReportData } from '../../features/analytics/services/reportService';
import paymentService from '../../features/payments/services/paymentService';
import invoiceService from '../../features/payments/services/invoiceService';
import type { Payment } from '../../features/payments/services/paymentService';
import type { Invoice } from '../../features/payments/services/invoiceService';
import { toast } from 'react-toastify';
import { Loading, EmptyState, ExportButton } from '../../components/common';
import { formatDate } from '../../utils/format';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { useAsync } from '../../hooks/useAsync';
import Loading from '../../shared/components/Loading';
import EmptyState from '../../shared/components/EmptyState';
import ExportButton from '../../shared/components/ExportButton';
import { formatDate } from '../../shared/utils/format';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import { useAsync } from '../../shared/hooks/useAsync';
import { useNavigate } from 'react-router-dom';
const AccountantDashboardPage: React.FC = () => {

View File

@@ -1,13 +1,13 @@
import React, { useEffect, useState } from 'react';
import { Search, Plus, Edit, Trash2, Eye, FileText, Filter } from 'lucide-react';
import { invoiceService, Invoice } from '../../services/api';
import invoiceService, { Invoice } from '../../features/payments/services/invoiceService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import Pagination from '../../components/common/Pagination';
import { ExportButton } from '../../components/common';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import Loading from '../../shared/components/Loading';
import Pagination from '../../shared/components/Pagination';
import ExportButton from '../../shared/components/ExportButton';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import { useNavigate } from 'react-router-dom';
import { formatDate } from '../../utils/format';
import { formatDate } from '../../shared/utils/format';
const InvoiceManagementPage: React.FC = () => {
const { formatCurrency } = useFormatCurrency();

View File

@@ -1,13 +1,13 @@
import React, { useEffect, useState } from 'react';
import { Search } from 'lucide-react';
import { paymentService } from '../../services/api';
import type { Payment } from '../../services/api/paymentService';
import paymentService from '../../features/payments/services/paymentService';
import type { Payment } from '../../features/payments/services/paymentService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import Pagination from '../../components/common/Pagination';
import { ExportButton } from '../../components/common';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { formatDate } from '../../utils/format';
import Loading from '../../shared/components/Loading';
import Pagination from '../../shared/components/Pagination';
import ExportButton from '../../shared/components/ExportButton';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import { formatDate } from '../../shared/utils/format';
const PaymentManagementPage: React.FC = () => {
const { formatCurrency } = useFormatCurrency();

View File

@@ -17,9 +17,9 @@ import {
Plus,
} from 'lucide-react';
import { toast } from 'react-toastify';
import { Loading } from '../../components/common';
import { useAsync } from '../../hooks/useAsync';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import Loading from '../../shared/components/Loading';
import { useAsync } from '../../shared/hooks/useAsync';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import analyticsService, {
ComprehensiveAnalyticsData,
RevPARData,
@@ -36,10 +36,10 @@ import analyticsService, {
ProfitLossData,
PaymentMethodAnalyticsData,
RefundAnalysisData,
} from '../../services/api/analyticsService';
import { SimpleBarChart, SimpleLineChart, SimplePieChart, KPICard } from '../../components/analytics/SimpleChart';
import { exportData } from '../../utils/exportUtils';
import CustomReportBuilder from '../../components/analytics/CustomReportBuilder';
} from '../../features/analytics/services/analyticsService';
import { SimpleBarChart, SimpleLineChart, SimplePieChart, KPICard } from '../../features/analytics/components/SimpleChart';
import { exportData } from '../../shared/utils/exportUtils';
import CustomReportBuilder from '../../features/analytics/components/CustomReportBuilder';
type AnalyticsCategory = 'revenue' | 'operational' | 'guest' | 'financial' | 'comprehensive';

View File

@@ -22,18 +22,18 @@ import {
Check,
} from 'lucide-react';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import Loading from '../../shared/components/Loading';
import advancedRoomService, {
RoomStatusBoardItem,
} from '../../services/api/advancedRoomService';
import { roomService, Room } from '../../services/api';
import MaintenanceManagement from '../../components/shared/MaintenanceManagement';
import HousekeepingManagement from '../../components/shared/HousekeepingManagement';
import InspectionManagement from '../../components/shared/InspectionManagement';
import Pagination from '../../components/common/Pagination';
import apiClient from '../../services/api/apiClient';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { logger } from '../../utils/logger';
} from '../../features/rooms/services/advancedRoomService';
import roomService, { Room } from '../../features/rooms/services/roomService';
import MaintenanceManagement from '../../features/hotel_services/components/MaintenanceManagement';
import HousekeepingManagement from '../../features/hotel_services/components/HousekeepingManagement';
import InspectionManagement from '../../features/hotel_services/components/InspectionManagement';
import Pagination from '../../shared/components/Pagination';
import apiClient from '../../shared/services/apiClient';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import { logger } from '../../shared/utils/logger';
type Tab = 'status-board' | 'maintenance' | 'housekeeping' | 'inspections' | 'rooms';

View File

@@ -29,15 +29,18 @@ import {
Award
} from 'lucide-react';
import { toast } from 'react-toastify';
import { Loading, EmptyState, ExportButton } from '../../components/common';
import Pagination from '../../components/common/Pagination';
import CurrencyIcon from '../../components/common/CurrencyIcon';
import { useAsync } from '../../hooks/useAsync';
import { reportService, ReportData, reviewService, Review } from '../../services/api';
import { auditService, AuditLog, AuditLogFilters } from '../../services/api/auditService';
import { formatDate } from '../../utils/format';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { logger } from '../../utils/logger';
import Loading from '../../shared/components/Loading';
import EmptyState from '../../shared/components/EmptyState';
import ExportButton from '../../shared/components/ExportButton';
import Pagination from '../../shared/components/Pagination';
import CurrencyIcon from '../../shared/components/CurrencyIcon';
import { useAsync } from '../../shared/hooks/useAsync';
import reportService, { ReportData } from '../../features/analytics/services/reportService';
import reviewService, { Review } from '../../features/reviews/services/reviewService';
import { auditService, AuditLog, AuditLogFilters } from '../../features/analytics/services/auditService';
import { formatDate } from '../../shared/utils/format';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import { logger } from '../../shared/utils/logger';
import analyticsService, {
ComprehensiveAnalyticsData,
RevPARData,
@@ -54,10 +57,10 @@ import analyticsService, {
ProfitLossData,
PaymentMethodAnalyticsData,
RefundAnalysisData,
} from '../../services/api/analyticsService';
import { SimpleBarChart, SimpleLineChart, SimplePieChart, KPICard } from '../../components/analytics/SimpleChart';
import { exportData } from '../../utils/exportUtils';
import CustomReportBuilder from '../../components/analytics/CustomReportBuilder';
} from '../../features/analytics/services/analyticsService';
import { SimpleBarChart, SimpleLineChart, SimplePieChart, KPICard } from '../../features/analytics/components/SimpleChart';
import { exportData } from '../../shared/utils/exportUtils';
import CustomReportBuilder from '../../features/analytics/components/CustomReportBuilder';
type AnalyticsTab = 'overview' | 'reports' | 'revenue' | 'operational' | 'guest' | 'financial' | 'audit-logs' | 'reviews';

View File

@@ -11,10 +11,11 @@ import {
Info
} from 'lucide-react';
import { toast } from 'react-toastify';
import { Loading, EmptyState } from '../../components/common';
import Pagination from '../../components/common/Pagination';
import { auditService, AuditLog, AuditLogFilters } from '../../services/api/auditService';
import { formatDate } from '../../utils/format';
import Loading from '../../shared/components/Loading';
import EmptyState from '../../shared/components/EmptyState';
import Pagination from '../../shared/components/Pagination';
import { auditService, AuditLog, AuditLogFilters } from '../../features/analytics/services/auditService';
import { formatDate } from '../../shared/utils/format';
const AuditLogsPage: React.FC = () => {
const [logs, setLogs] = useState<AuditLog[]>([]);

View File

@@ -1,11 +1,11 @@
import React, { useEffect, useState } from 'react';
import { Plus, Search, Edit, Trash2, X, Image as ImageIcon, Eye, EyeOff, Loader2 } from 'lucide-react';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import Pagination from '../../components/common/Pagination';
import { ConfirmationDialog } from '../../components/common';
import bannerServiceModule from '../../services/api/bannerService';
import type { Banner } from '../../services/api/bannerService';
import Loading from '../../shared/components/Loading';
import Pagination from '../../shared/components/Pagination';
import ConfirmationDialog from '../../shared/components/ConfirmationDialog';
import bannerServiceModule from '../../features/content/services/bannerService';
import type { Banner } from '../../features/content/services/bannerService';
const {
getAllBanners,

View File

@@ -1,10 +1,10 @@
import React, { useEffect, useState, useRef } from 'react';
import { Plus, Search, Edit, Trash2, X, Eye, EyeOff, Loader2, Calendar, User, Tag, GripVertical, Image as ImageIcon, Type, Quote, List, Video, ArrowRight, MoveUp, MoveDown, Sparkles, Upload } from 'lucide-react';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import Pagination from '../../components/common/Pagination';
import { ConfirmationDialog } from '../../components/common';
import { blogService, BlogPost, BlogPostCreate, BlogPostUpdate, BlogSection } from '../../services/api';
import Loading from '../../shared/components/Loading';
import Pagination from '../../shared/components/Pagination';
import ConfirmationDialog from '../../shared/components/ConfirmationDialog';
import blogService, { BlogPost, BlogPostCreate, BlogPostUpdate, BlogSection } from '../../features/content/services/blogService';
const BlogManagementPage: React.FC = () => {
const [posts, setPosts] = useState<BlogPost[]>([]);

View File

@@ -1,14 +1,15 @@
import React, { useEffect, useState } from 'react';
import { Search, Eye, XCircle, CheckCircle, Loader2, FileText, Plus, Mail } from 'lucide-react';
import { bookingService, Booking, invoiceService } from '../../services/api';
import bookingService, { Booking } from '../../features/bookings/services/bookingService';
import invoiceService from '../../features/payments/services/invoiceService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import Pagination from '../../components/common/Pagination';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { parseDateLocal } from '../../utils/format';
import Loading from '../../shared/components/Loading';
import Pagination from '../../shared/components/Pagination';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import { parseDateLocal } from '../../shared/utils/format';
import { useNavigate, useSearchParams } from 'react-router-dom';
import CreateBookingModal from '../../components/shared/CreateBookingModal';
import { logger } from '../../utils/logger';
import CreateBookingModal from '../../features/hotel_services/components/CreateBookingModal';
import { logger } from '../../shared/utils/logger';
const BookingManagementPage: React.FC = () => {
const { formatCurrency } = useFormatCurrency();

View File

@@ -14,16 +14,17 @@ import {
X,
} from 'lucide-react';
import { toast } from 'react-toastify';
import { Loading, EmptyState } from '../../components/common';
import Pagination from '../../components/common/Pagination';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { useCurrency } from '../../contexts/CurrencyContext';
import Loading from '../../shared/components/Loading';
import EmptyState from '../../shared/components/EmptyState';
import Pagination from '../../shared/components/Pagination';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import { useCurrency } from '../../features/payments/contexts/CurrencyContext';
import { useNavigate } from 'react-router-dom';
import { invoiceService, Invoice } from '../../services/api';
import { paymentService } from '../../services/api';
import type { Payment } from '../../services/api/paymentService';
import { promotionService, Promotion } from '../../services/api';
import { formatDate } from '../../utils/format';
import invoiceService, { Invoice } from '../../features/payments/services/invoiceService';
import paymentService from '../../features/payments/services/paymentService';
import type { Payment } from '../../features/payments/services/paymentService';
import promotionService, { Promotion } from '../../features/loyalty/services/promotionService';
import { formatDate } from '../../shared/utils/format';
type BusinessTab = 'overview' | 'invoices' | 'payments' | 'promotions';

View File

@@ -1,10 +1,10 @@
import React, { useState, useEffect } from 'react';
import { Search, User, Hotel, CheckCircle, AlertCircle, Calendar, LogIn, LogOut } from 'lucide-react';
import { bookingService, Booking } from '../../services/api';
import bookingService, { Booking } from '../../features/bookings/services/bookingService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { parseDateLocal } from '../../utils/format';
import Loading from '../../shared/components/Loading';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import { parseDateLocal } from '../../shared/utils/format';
interface GuestInfo {
name: string;

View File

@@ -1,11 +1,11 @@
import React, { useState } from 'react';
import { Search, FileText, CreditCard, Printer, CheckCircle } from 'lucide-react';
import { bookingService, Booking } from '../../services/api';
import bookingService, { Booking } from '../../features/bookings/services/bookingService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import CurrencyIcon from '../../components/common/CurrencyIcon';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { parseDateLocal } from '../../utils/format';
import Loading from '../../shared/components/Loading';
import CurrencyIcon from '../../shared/components/CurrencyIcon';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import { parseDateLocal } from '../../shared/utils/format';
interface ServiceItem {
service_name: string;

View File

@@ -6,8 +6,8 @@ import adminPrivacyService, {
CookieIntegrationSettingsResponse,
CookiePolicySettings,
CookiePolicySettingsResponse,
} from '../../services/api/adminPrivacyService';
import { Loading } from '../../components/common';
} from '../../features/content/services/adminPrivacyService';
import Loading from '../../shared/components/Loading';
const CookieSettingsPage: React.FC = () => {
const [policy, setPolicy] = useState<CookiePolicySettings>({

View File

@@ -1,10 +1,10 @@
import React, { useEffect, useState } from 'react';
import { DollarSign, Save, Info, Globe } from 'lucide-react';
import { toast } from 'react-toastify';
import systemSettingsService, { PlatformCurrencyResponse } from '../../services/api/systemSettingsService';
import { useCurrency } from '../../contexts/CurrencyContext';
import { Loading } from '../../components/common';
import { getCurrencySymbol } from '../../utils/format';
import systemSettingsService, { PlatformCurrencyResponse } from '../../features/system/services/systemSettingsService';
import { useCurrency } from '../../features/payments/contexts/CurrencyContext';
import Loading from '../../shared/components/Loading';
import { getCurrencySymbol } from '../../shared/utils/format';
const CurrencySettingsPage: React.FC = () => {
const { currency, supportedCurrencies, refreshCurrency } = useCurrency();

View File

@@ -9,17 +9,19 @@ import {
CreditCard,
LogOut
} from 'lucide-react';
import { reportService, ReportData, paymentService } from '../../services/api';
import type { Payment } from '../../services/api/paymentService';
import reportService, { ReportData } from '../../features/analytics/services/reportService';
import paymentService from '../../features/payments/services/paymentService';
import type { Payment } from '../../features/payments/services/paymentService';
import { toast } from 'react-toastify';
import { Loading, EmptyState } from '../../components/common';
import CurrencyIcon from '../../components/common/CurrencyIcon';
import { formatDate } from '../../utils/format';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { useAsync } from '../../hooks/useAsync';
import Loading from '../../shared/components/Loading';
import EmptyState from '../../shared/components/EmptyState';
import CurrencyIcon from '../../shared/components/CurrencyIcon';
import { formatDate } from '../../shared/utils/format';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import { useAsync } from '../../shared/hooks/useAsync';
import { useNavigate } from 'react-router-dom';
import useAuthStore from '../../store/useAuthStore';
import { logger } from '../../utils/logger';
import { logger } from '../../shared/utils/logger';
const DashboardPage: React.FC = () => {
const { formatCurrency } = useFormatCurrency();

View File

@@ -8,11 +8,11 @@ import {
Layers,
Target
} from 'lucide-react';
import { emailCampaignService, Campaign, CampaignSegment, EmailTemplate, DripSequence, CampaignAnalytics } from '../../services/api/emailCampaignService';
import { emailCampaignService, Campaign, CampaignSegment, EmailTemplate, DripSequence, CampaignAnalytics } from '../../features/notifications/services/emailCampaignService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import Pagination from '../../components/common/Pagination';
import { formatDate } from '../../utils/format';
import Loading from '../../shared/components/Loading';
import Pagination from '../../shared/components/Pagination';
import { formatDate } from '../../shared/utils/format';
type CampaignTab = 'campaigns' | 'segments' | 'templates' | 'drip-sequences' | 'analytics';

View File

@@ -1,12 +1,12 @@
import React, { useEffect, useState } from 'react';
import { Search, Eye, XCircle, CheckCircle, Loader2, Users, Plus } from 'lucide-react';
import { groupBookingService, GroupBooking } from '../../services/api';
import groupBookingService, { GroupBooking } from '../../features/bookings/services/groupBookingService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import Pagination from '../../components/common/Pagination';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { formatDate } from '../../utils/format';
import CreateGroupBookingModal from '../../components/shared/CreateGroupBookingModal';
import Loading from '../../shared/components/Loading';
import Pagination from '../../shared/components/Pagination';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import { formatDate } from '../../shared/utils/format';
import CreateGroupBookingModal from '../../features/hotel_services/components/CreateGroupBookingModal';
const GroupBookingManagementPage: React.FC = () => {
const { formatCurrency } = useFormatCurrency();

View File

@@ -16,11 +16,11 @@ import {
Phone,
MapPin,
} from 'lucide-react';
import { guestProfileService, GuestProfile, GuestListItem, GuestTag, GuestSegment, GuestSearchParams, GuestPreference } from '../../services/api';
import guestProfileService, { GuestProfile, GuestListItem, GuestTag, GuestSegment, GuestSearchParams, GuestPreference } from '../../features/guest_management/services/guestProfileService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import Pagination from '../../components/common/Pagination';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import Loading from '../../shared/components/Loading';
import Pagination from '../../shared/components/Pagination';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
type TabType = 'list' | 'profile';

View File

@@ -1,9 +1,9 @@
import React, { useEffect, useState } from 'react';
import { useParams, useNavigate, useLocation } from 'react-router-dom';
import { ArrowLeft, Save } from 'lucide-react';
import { invoiceService, Invoice, UpdateInvoiceData } from '../../services/api';
import invoiceService, { Invoice, UpdateInvoiceData } from '../../features/payments/services/invoiceService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import Loading from '../../shared/components/Loading';
const InvoiceEditPage: React.FC = () => {
const { id } = useParams<{ id: string }>();

View File

@@ -1,13 +1,13 @@
import React, { useEffect, useState } from 'react';
import { Search, Plus, Edit, Trash2, Eye, FileText, Filter } from 'lucide-react';
import { invoiceService, Invoice } from '../../services/api';
import invoiceService, { Invoice } from '../../features/payments/services/invoiceService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import Pagination from '../../components/common/Pagination';
import { ExportButton } from '../../components/common';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import Loading from '../../shared/components/Loading';
import Pagination from '../../shared/components/Pagination';
import ExportButton from '../../shared/components/ExportButton';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import { useNavigate } from 'react-router-dom';
import { formatDate } from '../../utils/format';
import { formatDate } from '../../shared/utils/format';
const InvoiceManagementPage: React.FC = () => {
const { formatCurrency } = useFormatCurrency();

View File

@@ -14,9 +14,11 @@ import {
Save
} from 'lucide-react';
import { toast } from 'react-toastify';
import { Loading, EmptyState, ConfirmationDialog } from '../../components/common';
import loyaltyService, { LoyaltyTier, LoyaltyReward } from '../../services/api/loyaltyService';
import Pagination from '../../components/common/Pagination';
import Loading from '../../shared/components/Loading';
import EmptyState from '../../shared/components/EmptyState';
import ConfirmationDialog from '../../shared/components/ConfirmationDialog';
import loyaltyService, { LoyaltyTier, LoyaltyReward } from '../../features/loyalty/services/loyaltyService';
import Pagination from '../../shared/components/Pagination';
type Tab = 'users' | 'tiers' | 'rewards';

View File

@@ -11,12 +11,13 @@ import {
Clock,
XCircle,
} from 'lucide-react';
import { Loading, EmptyState } from '../../components/common';
import { useAsync } from '../../hooks/useAsync';
import notificationService, { Notification } from '../../services/api/notificationService';
import { formatDate } from '../../utils/format';
import SendNotificationModal from '../../components/notifications/SendNotificationModal';
import NotificationTemplatesModal from '../../components/notifications/NotificationTemplatesModal';
import Loading from '../../shared/components/Loading';
import EmptyState from '../../shared/components/EmptyState';
import { useAsync } from '../../shared/hooks/useAsync';
import notificationService, { Notification } from '../../features/notifications/services/notificationService';
import { formatDate } from '../../shared/utils/format';
import SendNotificationModal from '../../features/notifications/components/SendNotificationModal';
import NotificationTemplatesModal from '../../features/notifications/components/NotificationTemplatesModal';
const NotificationManagementPage: React.FC = () => {
const [showSendModal, setShowSendModal] = useState(false);

View File

@@ -1,12 +1,12 @@
import React, { useEffect, useState } from 'react';
import { Plus, Search, Edit, Trash2, X, Package as PackageIcon } from 'lucide-react';
import { packageService, Package, PackageStatus, PackageItem, PackageItemType, CreatePackageData } from '../../services/api';
import { roomService, Room } from '../../services/api';
import { serviceService, Service } from '../../services/api';
import packageService, { Package, PackageStatus, PackageItem, PackageItemType, CreatePackageData } from '../../features/loyalty/services/packageService';
import roomService, { Room } from '../../features/rooms/services/roomService';
import serviceService, { Service } from '../../features/hotel_services/services/serviceService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import Pagination from '../../components/common/Pagination';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import Loading from '../../shared/components/Loading';
import Pagination from '../../shared/components/Pagination';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
const PackageManagementPage: React.FC = () => {
const { formatCurrency } = useFormatCurrency();

View File

@@ -23,11 +23,12 @@ import {
Accessibility,
HelpCircle
} from 'lucide-react';
import { pageContentService, PageContent, PageType, UpdatePageContentData, bannerService, Banner } from '../../services/api';
import pageContentService, { PageContent, PageType, UpdatePageContentData } from '../../features/content/services/pageContentService';
import bannerService, { Banner } from '../../features/content/services/bannerService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import { ConfirmationDialog } from '../../components/common';
import IconPicker from '../../components/admin/IconPicker';
import Loading from '../../shared/components/Loading';
import ConfirmationDialog from '../../shared/components/ConfirmationDialog';
import IconPicker from '../../features/system/components/IconPicker';
type ContentTab = 'overview' | 'home' | 'contact' | 'about' | 'footer' | 'seo' | 'privacy' | 'terms' | 'refunds' | 'cancellation' | 'accessibility' | 'faq';

View File

@@ -1,13 +1,13 @@
import React, { useEffect, useState } from 'react';
import { Search } from 'lucide-react';
import { paymentService } from '../../services/api';
import type { Payment } from '../../services/api/paymentService';
import paymentService from '../../features/payments/services/paymentService';
import type { Payment } from '../../features/payments/services/paymentService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import Pagination from '../../components/common/Pagination';
import { ExportButton } from '../../components/common';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { formatDate } from '../../utils/format';
import Loading from '../../shared/components/Loading';
import Pagination from '../../shared/components/Pagination';
import ExportButton from '../../shared/components/ExportButton';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import { formatDate } from '../../shared/utils/format';
const PaymentManagementPage: React.FC = () => {
const { formatCurrency } = useFormatCurrency();

View File

@@ -1,11 +1,11 @@
import React, { useEffect, useState } from 'react';
import { Plus, Search, Edit, Trash2, X, Tag } from 'lucide-react';
import { promotionService, Promotion } from '../../services/api';
import promotionService, { Promotion } from '../../features/loyalty/services/promotionService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import Pagination from '../../components/common/Pagination';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { useCurrency } from '../../contexts/CurrencyContext';
import Loading from '../../shared/components/Loading';
import Pagination from '../../shared/components/Pagination';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import { useCurrency } from '../../features/payments/contexts/CurrencyContext';
const PromotionManagementPage: React.FC = () => {
const { currency } = useCurrency();

View File

@@ -1,11 +1,11 @@
import React, { useEffect, useState } from 'react';
import { Plus, Search, Edit, Trash2, X, Tag } from 'lucide-react';
import { ratePlanService, RatePlan, RatePlanType, RatePlanStatus, CreateRatePlanData } from '../../services/api';
import { roomService, Room } from '../../services/api';
import ratePlanService, { RatePlan, RatePlanType, RatePlanStatus, CreateRatePlanData } from '../../features/rooms/services/ratePlanService';
import roomService, { Room } from '../../features/rooms/services/roomService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import Pagination from '../../components/common/Pagination';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import Loading from '../../shared/components/Loading';
import Pagination from '../../shared/components/Pagination';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
const RatePlanManagementPage: React.FC = () => {
const { formatCurrency } = useFormatCurrency();

View File

@@ -22,14 +22,15 @@ import {
Calendar,
Wrench
} from 'lucide-react';
import { bookingService, Booking, serviceService, Service } from '../../services/api';
import bookingService, { Booking } from '../../features/bookings/services/bookingService';
import serviceService, { Service } from '../../features/hotel_services/services/serviceService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import CurrencyIcon from '../../components/common/CurrencyIcon';
import Pagination from '../../components/common/Pagination';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { parseDateLocal } from '../../utils/format';
import CreateBookingModal from '../../components/shared/CreateBookingModal';
import Loading from '../../shared/components/Loading';
import CurrencyIcon from '../../shared/components/CurrencyIcon';
import Pagination from '../../shared/components/Pagination';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import { parseDateLocal } from '../../shared/utils/format';
import CreateBookingModal from '../../features/hotel_services/components/CreateBookingModal';
type ReceptionTab = 'overview' | 'check-in' | 'check-out' | 'bookings' | 'services';

View File

@@ -10,12 +10,13 @@ import {
PieChart
} from 'lucide-react';
import { toast } from 'react-toastify';
import { Loading, EmptyState } from '../../components/common';
import CurrencyIcon from '../../components/common/CurrencyIcon';
import { useAsync } from '../../hooks/useAsync';
import { reportService, ReportData } from '../../services/api';
import { formatDate } from '../../utils/format';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import Loading from '../../shared/components/Loading';
import EmptyState from '../../shared/components/EmptyState';
import CurrencyIcon from '../../shared/components/CurrencyIcon';
import { useAsync } from '../../shared/hooks/useAsync';
import reportService, { ReportData } from '../../features/analytics/services/reportService';
import { formatDate } from '../../shared/utils/format';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
const ReportsPage: React.FC = () => {
const { formatCurrency } = useFormatCurrency();

View File

@@ -1,8 +1,8 @@
import React, { useEffect, useState } from 'react';
import { reviewService, Review } from '../../services/api';
import reviewService, { Review } from '../../features/reviews/services/reviewService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import Pagination from '../../components/common/Pagination';
import Loading from '../../shared/components/Loading';
import Pagination from '../../shared/components/Pagination';
const ReviewManagementPage: React.FC = () => {
const [reviews, setReviews] = useState<Review[]>([]);

View File

@@ -1,11 +1,11 @@
import React, { useEffect, useState } from 'react';
import { Plus, Search, Edit, Trash2, X, Upload, Image as ImageIcon, Check } from 'lucide-react';
import { roomService, Room } from '../../services/api';
import roomService, { Room } from '../../features/rooms/services/roomService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import Pagination from '../../components/common/Pagination';
import apiClient from '../../services/api/apiClient';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import Loading from '../../shared/components/Loading';
import Pagination from '../../shared/components/Pagination';
import apiClient from '../../shared/services/apiClient';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
const RoomManagementPage: React.FC = () => {
const { formatCurrency } = useFormatCurrency();

View File

@@ -14,11 +14,11 @@ import {
AlertCircle,
Info
} from 'lucide-react';
import { securityService, SecurityEvent, SecurityStats, OAuthProvider, DataSubjectRequest } from '../../services/api/securityService';
import { securityService, SecurityEvent, SecurityStats, OAuthProvider, DataSubjectRequest } from '../../features/security/services/securityService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import Pagination from '../../components/common/Pagination';
import { formatDate } from '../../utils/format';
import Loading from '../../shared/components/Loading';
import Pagination from '../../shared/components/Pagination';
import { formatDate } from '../../shared/utils/format';
type SecurityTab = 'events' | 'stats' | 'ip-whitelist' | 'ip-blacklist' | 'oauth' | 'gdpr' | 'scan';

View File

@@ -1,10 +1,10 @@
import React, { useEffect, useState } from 'react';
import { Plus, Search, Edit, Trash2, X } from 'lucide-react';
import { serviceService, Service } from '../../services/api';
import serviceService, { Service } from '../../features/hotel_services/services/serviceService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import Pagination from '../../components/common/Pagination';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import Loading from '../../shared/components/Loading';
import Pagination from '../../shared/components/Pagination';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
const ServiceManagementPage: React.FC = () => {
const { formatCurrency } = useFormatCurrency();

View File

@@ -29,7 +29,7 @@ import adminPrivacyService, {
CookieIntegrationSettings,
CookiePolicySettings,
CookiePolicySettingsResponse,
} from '../../services/api/adminPrivacyService';
} from '../../features/content/services/adminPrivacyService';
import systemSettingsService, {
StripeSettingsResponse,
UpdateStripeSettingsRequest,
@@ -41,11 +41,11 @@ import systemSettingsService, {
UpdateSmtpSettingsRequest,
CompanySettingsResponse,
UpdateCompanySettingsRequest,
} from '../../services/api/systemSettingsService';
import { recaptchaService, RecaptchaSettingsAdminResponse, UpdateRecaptchaSettingsRequest } from '../../services/api/systemSettingsService';
import { useCurrency } from '../../contexts/CurrencyContext';
import { Loading } from '../../components/common';
import { getCurrencySymbol } from '../../utils/format';
} from '../../features/system/services/systemSettingsService';
import { recaptchaService, RecaptchaSettingsAdminResponse, UpdateRecaptchaSettingsRequest } from '../../features/system/services/systemSettingsService';
import { useCurrency } from '../../features/payments/contexts/CurrencyContext';
import Loading from '../../shared/components/Loading';
import { getCurrencySymbol } from '../../shared/utils/format';
type SettingsTab = 'general' | 'cookie' | 'currency' | 'payment' | 'smtp' | 'company' | 'recaptcha';

View File

@@ -4,8 +4,8 @@ import { toast } from 'react-toastify';
import systemSettingsService, {
StripeSettingsResponse,
UpdateStripeSettingsRequest,
} from '../../services/api/systemSettingsService';
import { Loading } from '../../components/common';
} from '../../features/system/services/systemSettingsService';
import Loading from '../../shared/components/Loading';
const StripeSettingsPage: React.FC = () => {
const [settings, setSettings] = useState<StripeSettingsResponse['data'] | null>(null);

View File

@@ -11,14 +11,15 @@ import {
Play,
} from 'lucide-react';
import { toast } from 'react-toastify';
import { Loading, EmptyState } from '../../components/common';
import { useAsync } from '../../hooks/useAsync';
import taskService, { Task, TaskStatistics } from '../../services/api/taskService';
import { formatDate } from '../../utils/format';
import TaskDetailModal from '../../components/tasks/TaskDetailModal';
import CreateTaskModal from '../../components/tasks/CreateTaskModal';
import TaskFilters from '../../components/tasks/TaskFilters';
import { logger } from '../../utils/logger';
import Loading from '../../shared/components/Loading';
import EmptyState from '../../shared/components/EmptyState';
import { useAsync } from '../../shared/hooks/useAsync';
import taskService, { Task, TaskStatistics } from '../../features/system/services/taskService';
import { formatDate } from '../../shared/utils/format';
import TaskDetailModal from '../../features/system/components/TaskDetailModal';
import CreateTaskModal from '../../features/system/components/CreateTaskModal';
import TaskFilters from '../../features/system/components/TaskFilters';
import { logger } from '../../shared/utils/logger';
type TaskStatus = 'pending' | 'assigned' | 'in_progress' | 'completed' | 'cancelled' | 'overdue';
type TaskPriority = 'low' | 'medium' | 'high' | 'urgent';

View File

@@ -1,14 +1,14 @@
import React, { useEffect, useState } from 'react';
import { Plus, Search, Edit, Trash2, X } from 'lucide-react';
import { userService, User } from '../../services/api';
import userService, { User } from '../../features/auth/services/userService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import LoadingButton from '../../components/common/LoadingButton';
import ErrorMessage from '../../components/common/ErrorMessage';
import Pagination from '../../components/common/Pagination';
import Loading from '../../shared/components/Loading';
import LoadingButton from '../../shared/components/LoadingButton';
import ErrorMessage from '../../shared/components/ErrorMessage';
import Pagination from '../../shared/components/Pagination';
import useAuthStore from '../../store/useAuthStore';
import { logger } from '../../utils/logger';
import { useApiCall } from '../../hooks/useApiCall';
import { logger } from '../../shared/utils/logger';
import { useApiCall } from '../../shared/hooks/useApiCall';
const UserManagementPage: React.FC = () => {
const { userInfo } = useAuthStore();

View File

@@ -9,11 +9,12 @@ import {
XCircle,
} from 'lucide-react';
import { toast } from 'react-toastify';
import { Loading, EmptyState } from '../../components/common';
import { useAsync } from '../../hooks/useAsync';
import workflowService, { Workflow } from '../../services/api/workflowService';
import WorkflowBuilder from '../../components/workflows/WorkflowBuilder';
import WorkflowDetailModal from '../../components/workflows/WorkflowDetailModal';
import Loading from '../../shared/components/Loading';
import EmptyState from '../../shared/components/EmptyState';
import { useAsync } from '../../shared/hooks/useAsync';
import workflowService, { Workflow } from '../../features/system/services/workflowService';
import WorkflowBuilder from '../../features/system/components/WorkflowBuilder';
import WorkflowDetailModal from '../../features/system/components/WorkflowDetailModal';
const WorkflowManagementPage: React.FC = () => {
const [showBuilder, setShowBuilder] = useState(false);

View File

@@ -26,15 +26,14 @@ import { toast } from 'react-toastify';
import {
getBookingById,
type Booking,
} from '../../services/api/bookingService';
} from '../../features/bookings/services/bookingService';
import useAuthStore from '../../store/useAuthStore';
import { useAuthModal } from '../../contexts/AuthModalContext';
import Loading from '../../components/common/Loading';
import PaymentStatusBadge from
'../../components/common/PaymentStatusBadge';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { parseDateLocal } from '../../utils/format';
import CancelBookingModal from '../../components/booking/CancelBookingModal';
import { useAuthModal } from '../../features/auth/contexts/AuthModalContext';
import Loading from '../../shared/components/Loading';
import PaymentStatusBadge from '../../shared/components/PaymentStatusBadge';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import { parseDateLocal } from '../../shared/utils/format';
import CancelBookingModal from '../../features/bookings/components/CancelBookingModal';
const BookingDetailPage: React.FC = () => {
const { id } = useParams<{ id: string }>();

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { Calendar, Clock } from 'lucide-react';
import CurrencyIcon from '../../components/common/CurrencyIcon';
import CurrencyIcon from '../../shared/components/CurrencyIcon';
const BookingListPage: React.FC = () => {
return (

View File

@@ -27,13 +27,12 @@ import {
getBookingById,
generateQRCode,
type Booking,
} from '../../services/api/bookingService';
import { confirmBankTransfer } from
'../../services/api/paymentService';
import Loading from '../../components/common/Loading';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { parseDateLocal } from '../../utils/format';
import DepositPaymentModal from '../../components/payments/DepositPaymentModal';
} from '../../features/bookings/services/bookingService';
import { confirmBankTransfer } from '../../features/payments/services/paymentService';
import Loading from '../../shared/components/Loading';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import { parseDateLocal } from '../../shared/utils/format';
import DepositPaymentModal from '../../features/payments/components/DepositPaymentModal';
const BookingSuccessPage: React.FC = () => {
const { id } = useParams<{ id: string }>();

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react';
import { useSearchParams, useNavigate } from 'react-router-dom';
import { confirmBoricaPayment } from '../../services/api/paymentService';
import { confirmBoricaPayment } from '../../features/payments/services/paymentService';
import { toast } from 'react-toastify';
import { CheckCircle, XCircle, Loader2 } from 'lucide-react';

View File

@@ -8,15 +8,16 @@ import {
CreditCard,
} from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import dashboardService, { CustomerDashboardStats } from '../../services/api/dashboardService';
import { paymentService } from '../../services/api';
import type { Payment } from '../../services/api/paymentService';
import dashboardService, { CustomerDashboardStats } from '../../features/analytics/services/dashboardService';
import paymentService from '../../features/payments/services/paymentService';
import type { Payment } from '../../features/payments/services/paymentService';
import { toast } from 'react-toastify';
import { formatDate, formatRelativeTime } from '../../utils/format';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { Loading, EmptyState } from '../../components/common';
import CurrencyIcon from '../../components/common/CurrencyIcon';
import { useAsync } from '../../hooks/useAsync';
import { formatDate, formatRelativeTime } from '../../shared/utils/format';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import Loading from '../../shared/components/Loading';
import EmptyState from '../../shared/components/EmptyState';
import CurrencyIcon from '../../shared/components/CurrencyIcon';
import { useAsync } from '../../shared/hooks/useAsync';
const DashboardPage: React.FC = () => {
const navigate = useNavigate();

View File

@@ -1,12 +1,11 @@
import React, { useEffect } from 'react';
import { Link } from 'react-router-dom';
import { Heart, AlertCircle, ArrowLeft } from 'lucide-react';
import { RoomCard, RoomCardSkeleton } from
'../../components/rooms';
import { RoomCard, RoomCardSkeleton } from '../../features/rooms/components';
import useFavoritesStore from
'../../store/useFavoritesStore';
import useAuthStore from '../../store/useAuthStore';
import { useAuthModal } from '../../contexts/AuthModalContext';
import { useAuthModal } from '../../features/auth/contexts/AuthModalContext';
const FavoritesPage: React.FC = () => {
const { isAuthenticated } = useAuthStore();

View File

@@ -7,16 +7,15 @@ import {
ArrowLeft,
} from 'lucide-react';
import { toast } from 'react-toastify';
import { getBookingById, type Booking } from
'../../services/api/bookingService';
import { getBookingById, type Booking } from '../../features/bookings/services/bookingService';
import {
getPaymentsByBookingId,
type Payment,
} from '../../services/api/paymentService';
import Loading from '../../components/common/Loading';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { parseDateLocal } from '../../utils/format';
import StripePaymentWrapper from '../../components/payments/StripePaymentWrapper';
} from '../../features/payments/services/paymentService';
import Loading from '../../shared/components/Loading';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import { parseDateLocal } from '../../shared/utils/format';
import StripePaymentWrapper from '../../features/payments/components/StripePaymentWrapper';
const FullPaymentPage: React.FC = () => {
const { bookingId } = useParams<{ bookingId: string }>();

View File

@@ -1,11 +1,11 @@
import React, { useState, useEffect } from 'react';
import { Users, Calendar, Building2, ArrowRight } from 'lucide-react';
import { groupBookingService, GroupBooking } from '../../services/api';
import groupBookingService, { GroupBooking } from '../../features/bookings/services/groupBookingService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { formatDate } from '../../utils/format';
import CreateGroupBookingModal from '../../components/shared/CreateGroupBookingModal';
import Loading from '../../shared/components/Loading';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import { formatDate } from '../../shared/utils/format';
import CreateGroupBookingModal from '../../features/hotel_services/components/CreateGroupBookingModal';
import { useNavigate } from 'react-router-dom';
const GroupBookingPage: React.FC = () => {

View File

@@ -1,11 +1,11 @@
import React, { useEffect, useState } from 'react';
import { useParams, useNavigate, Link } from 'react-router-dom';
import { ArrowLeft, Download, FileText, CheckCircle, Clock, XCircle } from 'lucide-react';
import { invoiceService, Invoice } from '../../services/api';
import invoiceService, { Invoice } from '../../features/payments/services/invoiceService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { formatDate } from '../../utils/format';
import Loading from '../../shared/components/Loading';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import { formatDate } from '../../shared/utils/format';
import useAuthStore from '../../store/useAuthStore';
const InvoicePage: React.FC = () => {

View File

@@ -12,15 +12,16 @@ import {
CreditCard
} from 'lucide-react';
import { toast } from 'react-toastify';
import { Loading, EmptyState } from '../../components/common';
import Loading from '../../shared/components/Loading';
import EmptyState from '../../shared/components/EmptyState';
import loyaltyService, {
UserLoyaltyStatus,
PointsTransaction,
LoyaltyReward,
RewardRedemption,
Referral
} from '../../services/api/loyaltyService';
import { formatDate } from '../../utils/format';
} from '../../features/loyalty/services/loyaltyService';
import { formatDate } from '../../shared/utils/format';
type Tab = 'overview' | 'rewards' | 'history' | 'referrals';

View File

@@ -19,14 +19,14 @@ import { toast } from 'react-toastify';
import {
getMyBookings,
type Booking,
} from '../../services/api/bookingService';
import CancelBookingModal from '../../components/booking/CancelBookingModal';
} from '../../features/bookings/services/bookingService';
import CancelBookingModal from '../../features/bookings/components/CancelBookingModal';
import useAuthStore from '../../store/useAuthStore';
import { useAuthModal } from '../../contexts/AuthModalContext';
import Loading from '../../components/common/Loading';
import EmptyState from '../../components/common/EmptyState';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { parseDateLocal } from '../../utils/format';
import { useAuthModal } from '../../features/auth/contexts/AuthModalContext';
import Loading from '../../shared/components/Loading';
import EmptyState from '../../shared/components/EmptyState';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import { parseDateLocal } from '../../shared/utils/format';
const MyBookingsPage: React.FC = () => {
const { isAuthenticated } = useAuthStore();

View File

@@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react';
import { useSearchParams, useNavigate } from 'react-router-dom';
import { XCircle, ArrowLeft, Loader2 } from 'lucide-react';
import { cancelPayPalPayment } from '../../services/api/paymentService';
import { cancelPayPalPayment } from '../../features/payments/services/paymentService';
import { toast } from 'react-toastify';
const PayPalCancelPage: React.FC = () => {

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react';
import { useSearchParams, useNavigate } from 'react-router-dom';
import { capturePayPalPayment, cancelPayPalPayment } from '../../services/api/paymentService';
import { capturePayPalPayment, cancelPayPalPayment } from '../../features/payments/services/paymentService';
import { toast } from 'react-toastify';
import { CheckCircle, XCircle, Loader2 } from 'lucide-react';

View File

@@ -19,15 +19,13 @@ import {
getBookingById,
generateQRCode,
type Booking,
} from '../../services/api/bookingService';
import { confirmBankTransfer } from
'../../services/api/paymentService';
} from '../../features/bookings/services/bookingService';
import { confirmBankTransfer } from '../../features/payments/services/paymentService';
import useAuthStore from '../../store/useAuthStore';
import { useAuthModal } from '../../contexts/AuthModalContext';
import Loading from '../../components/common/Loading';
import PaymentStatusBadge from
'../../components/common/PaymentStatusBadge';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { useAuthModal } from '../../features/auth/contexts/AuthModalContext';
import Loading from '../../shared/components/Loading';
import PaymentStatusBadge from '../../shared/components/PaymentStatusBadge';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
const PaymentConfirmationPage: React.FC = () => {
const { id } = useParams<{ id: string }>();

View File

@@ -8,7 +8,7 @@ import {
Receipt,
Loader2,
} from 'lucide-react';
import { useCompanySettings } from '../../contexts/CompanySettingsContext';
import { useCompanySettings } from '../../shared/contexts/CompanySettingsContext';
const PaymentResultPage: React.FC = () => {
const [searchParams] = useSearchParams();

View File

@@ -21,12 +21,13 @@ import {
KeyRound
} from 'lucide-react';
import { toast } from 'react-toastify';
import authService from '../../services/api/authService';
import authService from '../../features/auth/services/authService';
import useAuthStore from '../../store/useAuthStore';
import { Loading, EmptyState } from '../../components/common';
import { useAsync } from '../../hooks/useAsync';
import { useGlobalLoading } from '../../contexts/LoadingContext';
import { normalizeImageUrl } from '../../utils/imageUtils';
import Loading from '../../shared/components/Loading';
import EmptyState from '../../shared/components/EmptyState';
import { useAsync } from '../../shared/hooks/useAsync';
import { useGlobalLoading } from '../../shared/contexts/LoadingContext';
import { normalizeImageUrl } from '../../shared/utils/imageUtils';
const profileValidationSchema = yup.object().shape({
name: yup
@@ -200,7 +201,7 @@ const ProfilePage: React.FC = () => {
}
}
} else {
const { updateUser } = await import('../../services/api/userService');
const { updateUser } = await import('../../features/auth/services/userService');
const user = userInfo && 'user' in userInfo ? userInfo.user : userInfo;
const response = await updateUser((user as any)?.id || (userInfo as any)?.id, {
full_name: data.name,
@@ -251,7 +252,7 @@ const ProfilePage: React.FC = () => {
resetPassword();
}
} else {
const { updateUser } = await import('../../services/api/userService');
const { updateUser } = await import('../../features/auth/services/userService');
const user = userInfo && 'user' in userInfo ? userInfo.user : userInfo;
const response = await updateUser((user as any)?.id || (userInfo as any)?.id, {
password: data.newPassword,

View File

@@ -10,16 +10,15 @@ import {
Award,
Sparkles,
} from 'lucide-react';
import { getRoomByNumber, getRoomBookedDates, type Room } from
'../../services/api/roomService';
import RoomGallery from '../../components/rooms/RoomGallery';
import RoomAmenities from '../../components/rooms/RoomAmenities';
import ReviewSection from '../../components/rooms/ReviewSection';
import CurrencyIcon from '../../components/common/CurrencyIcon';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { getRoomByNumber, getRoomBookedDates, type Room } from '../../features/rooms/services/roomService';
import RoomGallery from '../../features/rooms/components/RoomGallery';
import RoomAmenities from '../../features/rooms/components/RoomAmenities';
import ReviewSection from '../../features/rooms/components/ReviewSection';
import CurrencyIcon from '../../shared/components/CurrencyIcon';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import useAuthStore from '../../store/useAuthStore';
import LuxuryBookingModal from '../../components/booking/LuxuryBookingModal';
import { useAuthModal } from '../../contexts/AuthModalContext';
import LuxuryBookingModal from '../../features/bookings/components/LuxuryBookingModal';
import { useAuthModal } from '../../features/auth/contexts/AuthModalContext';
import { toast } from 'react-toastify';
const RoomDetailPage: React.FC = () => {

View File

@@ -1,11 +1,11 @@
import React, { useState, useEffect, useRef } from 'react';
import { useSearchParams, Link } from 'react-router-dom';
import { getRooms } from '../../services/api/roomService';
import type { Room } from '../../services/api/roomService';
import RoomFilter from '../../components/rooms/RoomFilter';
import RoomCard from '../../components/rooms/RoomCard';
import RoomCardSkeleton from '../../components/rooms/RoomCardSkeleton';
import Pagination from '../../components/rooms/Pagination';
import { getRooms } from '../../features/rooms/services/roomService';
import type { Room } from '../../features/rooms/services/roomService';
import RoomFilter from '../../features/rooms/components/RoomFilter';
import RoomCard from '../../features/rooms/components/RoomCard';
import RoomCardSkeleton from '../../features/rooms/components/RoomCardSkeleton';
import Pagination from '../../shared/components/Pagination';
import { ArrowLeft, Hotel, Filter, ChevronDown, ChevronUp } from 'lucide-react';
const RoomListPage: React.FC = () => {

View File

@@ -12,16 +12,13 @@ import {
Home,
Users,
} from 'lucide-react';
import {
RoomCard,
RoomCardSkeleton,
Pagination,
} from '../../components/rooms';
import { searchAvailableRooms } from
'../../services/api/roomService';
import type { Room } from '../../services/api/roomService';
import RoomCard from '../../features/rooms/components/RoomCard';
import RoomCardSkeleton from '../../features/rooms/components/RoomCardSkeleton';
import Pagination from '../../shared/components/Pagination';
import { searchAvailableRooms } from '../../features/rooms/services/roomService';
import type { Room } from '../../features/rooms/services/roomService';
import { toast } from 'react-toastify';
import { parseDateLocal } from '../../utils/format';
import { parseDateLocal } from '../../shared/utils/format';
const SearchResultsPage: React.FC = () => {
const [searchParams] = useSearchParams();

View File

@@ -15,14 +15,14 @@ import {
MapPin,
} from 'lucide-react';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import Loading from '../../shared/components/Loading';
import advancedRoomService, {
RoomStatusBoardItem,
} from '../../services/api/advancedRoomService';
import { roomService } from '../../services/api';
import MaintenanceManagement from '../../components/shared/MaintenanceManagement';
import HousekeepingManagement from '../../components/shared/HousekeepingManagement';
import InspectionManagement from '../../components/shared/InspectionManagement';
} from '../../features/rooms/services/advancedRoomService';
import roomService from '../../features/rooms/services/roomService';
import MaintenanceManagement from '../../features/hotel_services/components/MaintenanceManagement';
import HousekeepingManagement from '../../features/hotel_services/components/HousekeepingManagement';
import InspectionManagement from '../../features/hotel_services/components/InspectionManagement';
type Tab = 'status-board' | 'maintenance' | 'housekeeping' | 'inspections';

View File

@@ -29,14 +29,17 @@ import {
Award
} from 'lucide-react';
import { toast } from 'react-toastify';
import { Loading, EmptyState, ExportButton } from '../../components/common';
import Pagination from '../../components/common/Pagination';
import CurrencyIcon from '../../components/common/CurrencyIcon';
import { useAsync } from '../../hooks/useAsync';
import { reportService, ReportData, reviewService, Review } from '../../services/api';
import { auditService, AuditLog, AuditLogFilters } from '../../services/api/auditService';
import { formatDate } from '../../utils/format';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import Loading from '../../shared/components/Loading';
import EmptyState from '../../shared/components/EmptyState';
import ExportButton from '../../shared/components/ExportButton';
import Pagination from '../../shared/components/Pagination';
import CurrencyIcon from '../../shared/components/CurrencyIcon';
import { useAsync } from '../../shared/hooks/useAsync';
import reportService, { ReportData } from '../../features/analytics/services/reportService';
import reviewService, { Review } from '../../features/reviews/services/reviewService';
import { auditService, AuditLog, AuditLogFilters } from '../../features/analytics/services/auditService';
import { formatDate } from '../../shared/utils/format';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import analyticsService, {
ComprehensiveAnalyticsData,
RevPARData,
@@ -53,10 +56,10 @@ import analyticsService, {
ProfitLossData,
PaymentMethodAnalyticsData,
RefundAnalysisData,
} from '../../services/api/analyticsService';
import { SimpleBarChart, SimpleLineChart, SimplePieChart, KPICard } from '../../components/analytics/SimpleChart';
import { exportData } from '../../utils/exportUtils';
import CustomReportBuilder from '../../components/analytics/CustomReportBuilder';
} from '../../features/analytics/services/analyticsService';
import { SimpleBarChart, SimpleLineChart, SimplePieChart, KPICard } from '../../features/analytics/components/SimpleChart';
import { exportData } from '../../shared/utils/exportUtils';
import CustomReportBuilder from '../../features/analytics/components/CustomReportBuilder';
type AnalyticsTab = 'overview' | 'reports' | 'revenue' | 'operational' | 'guest' | 'financial' | 'audit-logs' | 'reviews';

View File

@@ -1,13 +1,14 @@
import React, { useEffect, useState } from 'react';
import { Search, Eye, XCircle, CheckCircle, Loader2, FileText, Plus } from 'lucide-react';
import { bookingService, Booking, invoiceService } from '../../services/api';
import bookingService, { Booking } from '../../features/bookings/services/bookingService';
import invoiceService from '../../features/payments/services/invoiceService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import Pagination from '../../components/common/Pagination';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { parseDateLocal } from '../../utils/format';
import Loading from '../../shared/components/Loading';
import Pagination from '../../shared/components/Pagination';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import { parseDateLocal } from '../../shared/utils/format';
import { useNavigate } from 'react-router-dom';
import CreateBookingModal from '../../components/shared/CreateBookingModal';
import CreateBookingModal from '../../features/hotel_services/components/CreateBookingModal';
const BookingManagementPage: React.FC = () => {
const { formatCurrency } = useFormatCurrency();

View File

@@ -1,11 +1,12 @@
import React, { useState, useEffect, useRef } from 'react';
import { CheckCircle, XCircle, Send, Clock, User, X, RefreshCw } from 'lucide-react';
import { chatService, type Chat, type ChatMessage } from '../../services/api';
import chatService, { type Chat, type ChatMessage } from '../../features/notifications/services/chatService';
import { toast } from 'react-toastify';
import { Loading, EmptyState } from '../../components/common';
import { formatDate } from '../../utils/format';
import Loading from '../../shared/components/Loading';
import EmptyState from '../../shared/components/EmptyState';
import { formatDate } from '../../shared/utils/format';
import useAuthStore from '../../store/useAuthStore';
import { useChatNotifications } from '../../contexts/ChatNotificationContext';
import { useChatNotifications } from '../../features/notifications/contexts/ChatNotificationContext';
const ChatManagementPage: React.FC = () => {
const [chats, setChats] = useState<Chat[]>([]);

View File

@@ -6,14 +6,17 @@ import {
RefreshCw,
CreditCard
} from 'lucide-react';
import { reportService, ReportData, paymentService, bookingService } from '../../services/api';
import type { Payment } from '../../services/api/paymentService';
import type { Booking } from '../../services/api/bookingService';
import reportService, { ReportData } from '../../features/analytics/services/reportService';
import paymentService from '../../features/payments/services/paymentService';
import bookingService from '../../features/bookings/services/bookingService';
import type { Payment } from '../../features/payments/services/paymentService';
import type { Booking } from '../../features/bookings/services/bookingService';
import { toast } from 'react-toastify';
import { Loading, EmptyState } from '../../components/common';
import { formatDate } from '../../utils/format';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { useAsync } from '../../hooks/useAsync';
import Loading from '../../shared/components/Loading';
import EmptyState from '../../shared/components/EmptyState';
import { formatDate } from '../../shared/utils/format';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import { useAsync } from '../../shared/hooks/useAsync';
import { useNavigate } from 'react-router-dom';
const StaffDashboardPage: React.FC = () => {

View File

@@ -16,11 +16,11 @@ import {
Phone,
MapPin,
} from 'lucide-react';
import { guestProfileService, GuestProfile, GuestListItem, GuestTag, GuestSegment, GuestSearchParams, GuestPreference } from '../../services/api';
import guestProfileService, { GuestProfile, GuestListItem, GuestTag, GuestSegment, GuestSearchParams, GuestPreference } from '../../features/guest_management/services/guestProfileService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import Pagination from '../../components/common/Pagination';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import Loading from '../../shared/components/Loading';
import Pagination from '../../shared/components/Pagination';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
type TabType = 'list' | 'profile';

View File

@@ -14,9 +14,11 @@ import {
Save
} from 'lucide-react';
import { toast } from 'react-toastify';
import { Loading, EmptyState, ConfirmationDialog } from '../../components/common';
import loyaltyService, { LoyaltyTier, LoyaltyReward } from '../../services/api/loyaltyService';
import Pagination from '../../components/common/Pagination';
import Loading from '../../shared/components/Loading';
import EmptyState from '../../shared/components/EmptyState';
import ConfirmationDialog from '../../shared/components/ConfirmationDialog';
import loyaltyService, { LoyaltyTier, LoyaltyReward } from '../../features/loyalty/services/loyaltyService';
import Pagination from '../../shared/components/Pagination';
type Tab = 'users' | 'tiers' | 'rewards';

View File

@@ -1,13 +1,13 @@
import React, { useEffect, useState } from 'react';
import { Search } from 'lucide-react';
import { paymentService } from '../../services/api';
import type { Payment } from '../../services/api/paymentService';
import paymentService from '../../features/payments/services/paymentService';
import type { Payment } from '../../features/payments/services/paymentService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import Pagination from '../../components/common/Pagination';
import { ExportButton } from '../../components/common';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { formatDate } from '../../utils/format';
import Loading from '../../shared/components/Loading';
import Pagination from '../../shared/components/Pagination';
import ExportButton from '../../shared/components/ExportButton';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import { formatDate } from '../../shared/utils/format';
const PaymentManagementPage: React.FC = () => {
const { formatCurrency } = useFormatCurrency();

View File

@@ -24,15 +24,17 @@ import {
Calendar,
Wrench
} from 'lucide-react';
import { bookingService, Booking, roomService, Room, serviceService, Service } from '../../services/api';
import bookingService, { Booking } from '../../features/bookings/services/bookingService';
import roomService, { Room } from '../../features/rooms/services/roomService';
import serviceService, { Service } from '../../features/hotel_services/services/serviceService';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import CurrencyIcon from '../../components/common/CurrencyIcon';
import Pagination from '../../components/common/Pagination';
import apiClient from '../../services/api/apiClient';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { parseDateLocal } from '../../utils/format';
import CreateBookingModal from '../../components/shared/CreateBookingModal';
import Loading from '../../shared/components/Loading';
import CurrencyIcon from '../../shared/components/CurrencyIcon';
import Pagination from '../../shared/components/Pagination';
import apiClient from '../../shared/services/apiClient';
import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency';
import { parseDateLocal } from '../../shared/utils/format';
import CreateBookingModal from '../../features/hotel_services/components/CreateBookingModal';
type ReceptionTab = 'overview' | 'check-in' | 'check-out' | 'bookings' | 'rooms' | 'services';