This commit is contained in:
Iliyan Angelov
2025-12-09 00:14:21 +02:00
parent b818d645a9
commit e43a95eafb
43 changed files with 2070 additions and 772 deletions

View File

@@ -6,8 +6,29 @@ import pageContentService, { PageContent } from '../services/pageContentService'
import serviceService, { Service } from '../../hotel_services/services/serviceService';
import Loading from '../../../shared/components/Loading';
import { useFormatCurrency } from '../../payments/hooks/useFormatCurrency';
import { useTheme } from '../../../shared/contexts/ThemeContext';
import { getThemeBackgroundClasses, getThemeHeroBackgroundClasses, getThemeTextClasses, getThemeCardClasses, getThemeInputClasses } from '../../../shared/utils/themeUtils';
// Helper function to get icon component from icon name (handles both PascalCase and lowercase)
const getIconComponent = (iconName?: string, fallback: any = Award) => {
if (!iconName) return fallback;
// Try direct match first (for PascalCase names)
if ((LucideIcons as any)[iconName]) {
return (LucideIcons as any)[iconName];
}
// Convert to PascalCase (capitalize first letter)
const pascalCaseName = iconName.charAt(0).toUpperCase() + iconName.slice(1).toLowerCase();
if ((LucideIcons as any)[pascalCaseName]) {
return (LucideIcons as any)[pascalCaseName];
}
return fallback;
};
const ServicesPage: React.FC = () => {
const { theme } = useTheme();
const [pageContent, setPageContent] = useState<PageContent | null>(null);
const [hotelServices, setHotelServices] = useState<Service[]>([]);
const [loading, setLoading] = useState(true);
@@ -156,10 +177,16 @@ const ServicesPage: React.FC = () => {
return <Loading />;
}
const bgClasses = getThemeBackgroundClasses(theme.theme_layout_mode);
const heroBgClasses = getThemeHeroBackgroundClasses(theme.theme_layout_mode);
const textClasses = getThemeTextClasses(theme.theme_layout_mode);
const cardClasses = getThemeCardClasses(theme.theme_layout_mode);
const inputClasses = getThemeInputClasses(theme.theme_layout_mode);
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={`min-h-screen ${bgClasses} 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-[var(--luxury-gold)]/10 pt-6 sm:pt-7 md:pt-8 overflow-hidden relative">
<div className={`w-full ${heroBgClasses} 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-[var(--luxury-gold)] rounded-full blur-3xl"></div>
@@ -172,18 +199,20 @@ const ServicesPage: React.FC = () => {
<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-[var(--luxury-gold)] via-[var(--luxury-gold-light)] to-[var(--luxury-gold-dark)] 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-[var(--luxury-gold)]/40 backdrop-blur-sm shadow-xl shadow-[var(--luxury-gold)]/20 group-hover:border-[var(--luxury-gold)]/60 transition-all duration-300">
<div className={`relative p-2 sm:p-2.5 md:p-3 ${cardClasses} rounded-lg border-2 border-[var(--luxury-gold)]/40 backdrop-blur-sm shadow-xl shadow-[var(--luxury-gold)]/20 group-hover:border-[var(--luxury-gold)]/60 transition-all duration-300`}>
<Award className="w-5 h-5 sm:w-6 sm:h-6 md:w-7 md:h-7 text-[var(--luxury-gold)] 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-[var(--luxury-gold)] to-white bg-clip-text text-transparent">
<span className={`${theme.theme_layout_mode === 'light'
? 'bg-gradient-to-r from-gray-900 via-[var(--luxury-gold)] to-gray-900'
: 'bg-gradient-to-r from-white via-[var(--luxury-gold)] to-white'} bg-clip-text text-transparent`}>
{pageContent?.luxury_services_section_title || 'Our Services'}
</span>
</h1>
<div className="w-12 sm:w-16 md:w-20 h-0.5 bg-gradient-to-r from-transparent via-[var(--luxury-gold)] 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">
<p className={`text-sm sm:text-base md:text-lg ${textClasses.secondary} font-light leading-relaxed max-w-xl mx-auto tracking-wide px-2 sm:px-4`}>
{pageContent?.luxury_services_section_subtitle || 'Discover our premium services designed to enhance your stay'}
</p>
<div className="mt-4 flex items-center justify-center gap-2 text-[var(--luxury-gold)]/60">
@@ -211,7 +240,7 @@ const ServicesPage: React.FC = () => {
placeholder="Search services..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-14 pr-5 py-4 bg-gradient-to-br from-[#1a1a1a] to-[#0f0f0f] border border-[var(--luxury-gold)]/20 rounded-xl text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[var(--luxury-gold)]/50 focus:border-[var(--luxury-gold)]/50 transition-all duration-300 backdrop-blur-sm font-light"
className={`w-full pl-14 pr-5 py-4 ${inputClasses} border border-[var(--luxury-gold)]/20 rounded-xl placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[var(--luxury-gold)]/50 focus:border-[var(--luxury-gold)]/50 transition-all duration-300 backdrop-blur-sm font-light`}
/>
</div>
</div>
@@ -221,13 +250,13 @@ const ServicesPage: React.FC = () => {
{/* Categories Filter - Top Center */}
{allCategories.length > 0 && (
<div className="mb-12 flex justify-center">
<div className="inline-flex flex-wrap items-center gap-3 bg-gradient-to-br from-[#1a1a1a] via-[#0f0f0f] to-[#0a0a0a] rounded-2xl border-2 border-[var(--luxury-gold)]/20 p-4 backdrop-blur-xl shadow-2xl">
<div className={`inline-flex flex-wrap items-center gap-3 ${cardClasses} rounded-2xl border-2 border-[var(--luxury-gold)]/20 p-4 backdrop-blur-xl shadow-2xl`}>
<button
onClick={() => setSelectedCategory(null)}
className={`group relative px-6 py-3 rounded-xl text-sm font-medium transition-all duration-300 overflow-hidden ${
selectedCategory === null
? 'bg-gradient-to-r from-[var(--luxury-gold)] to-[var(--luxury-gold-dark)] text-[#0f0f0f] shadow-lg shadow-[var(--luxury-gold)]/40'
: 'bg-gradient-to-br from-[#0f0f0f] to-[#0a0a0a] text-gray-300 border-2 border-[var(--luxury-gold)]/20 hover:border-[var(--luxury-gold)]/50 hover:text-[var(--luxury-gold)]'
: `${cardClasses} ${textClasses.secondary} border-2 border-[var(--luxury-gold)]/20 hover:border-[var(--luxury-gold)]/50 hover:text-[var(--luxury-gold)] ${theme.theme_layout_mode === 'light' ? 'hover:bg-gray-100' : 'hover:bg-[#1a1a1a]'}`
}`}
>
<span className="relative z-10 flex items-center gap-2">
@@ -242,7 +271,7 @@ const ServicesPage: React.FC = () => {
className={`group relative px-6 py-3 rounded-xl text-sm font-medium transition-all duration-300 overflow-hidden ${
selectedCategory === category
? 'bg-gradient-to-r from-[var(--luxury-gold)] to-[var(--luxury-gold-dark)] text-[#0f0f0f] shadow-lg shadow-[var(--luxury-gold)]/40'
: 'bg-gradient-to-br from-[#0f0f0f] to-[#0a0a0a] text-gray-300 border-2 border-[var(--luxury-gold)]/20 hover:border-[var(--luxury-gold)]/50 hover:text-[var(--luxury-gold)]'
: `${cardClasses} ${textClasses.secondary} border-2 border-[var(--luxury-gold)]/20 hover:border-[var(--luxury-gold)]/50 hover:text-[var(--luxury-gold)] ${theme.theme_layout_mode === 'light' ? 'hover:bg-gray-100' : 'hover:bg-[#1a1a1a]'}`
}`}
>
<span className="relative z-10 flex items-center gap-2">
@@ -259,7 +288,7 @@ const ServicesPage: React.FC = () => {
<div className="flex flex-col items-center">
{/* Results Count */}
{!loading && filteredServices.length > 0 && (
<div className="mb-8 text-gray-400 font-light text-sm text-center">
<div className={`mb-8 ${textClasses.muted} font-light text-sm text-center`}>
Showing {filteredServices.length} of {allServices.length} services
</div>
)}
@@ -270,8 +299,8 @@ const ServicesPage: React.FC = () => {
<div className="inline-flex items-center justify-center w-20 h-20 rounded-full bg-[var(--luxury-gold)]/10 mb-6">
<Award className="w-10 h-10 text-[var(--luxury-gold)]" />
</div>
<p className="text-gray-400 text-xl font-light">No services found</p>
<p className="text-gray-500 text-sm mt-2">Try adjusting your search or filters</p>
<p className={`${textClasses.muted} text-xl font-light`}>No services found</p>
<p className={`${textClasses.muted} text-sm mt-2`}>Try adjusting your search or filters</p>
</div>
) : (
<>
@@ -288,7 +317,7 @@ const ServicesPage: React.FC = () => {
<Link
key={service.id}
to={`/services/${serviceSlug}`}
className="group relative bg-gradient-to-br from-[#1a1a1a] via-[#0f0f0f] to-[#0a0a0a] rounded-3xl border-2 border-[var(--luxury-gold)]/20 overflow-hidden hover:border-[var(--luxury-gold)]/60 transition-all duration-700 hover:shadow-2xl hover:shadow-[var(--luxury-gold)]/30 hover:-translate-y-3 block"
className={`group relative ${cardClasses} rounded-3xl border-2 border-[var(--luxury-gold)]/20 overflow-hidden hover:border-[var(--luxury-gold)]/60 transition-all duration-700 hover:shadow-2xl hover:shadow-[var(--luxury-gold)]/30 hover:-translate-y-3 block`}
style={{ animationDelay: `${index * 50}ms` }}
>
{/* Premium Glow Effects */}
@@ -320,7 +349,7 @@ const ServicesPage: React.FC = () => {
<div className="absolute top-0 right-0 w-32 h-32 bg-gradient-to-br from-[var(--luxury-gold)]/20 to-transparent rounded-bl-full opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
</div>
) : (
<div className="h-48 sm:h-56 bg-gradient-to-br from-[#1a1a1a] to-[#0f0f0f] flex items-center justify-center p-8">
<div className={`h-48 sm:h-56 ${cardClasses} flex items-center justify-center p-8`}>
{service.icon && (LucideIcons as any)[service.icon] ? (
<div className="relative">
<div className="absolute inset-0 bg-[var(--luxury-gold)]/20 rounded-full blur-2xl"></div>
@@ -331,7 +360,12 @@ const ServicesPage: React.FC = () => {
) : (
<div className="relative">
<div className="absolute inset-0 bg-[var(--luxury-gold)]/20 rounded-full blur-2xl"></div>
<Award className="w-16 h-16 sm:w-20 sm:h-20 text-[var(--luxury-gold)] relative z-10 drop-shadow-lg" />
{React.createElement(
getIconComponent(pageContent?.services_fallback_icon, Award),
{
className: 'w-16 h-16 sm:w-20 sm:h-20 text-[var(--luxury-gold)] relative z-10 drop-shadow-lg'
}
)}
</div>
)}
</div>
@@ -345,11 +379,11 @@ const ServicesPage: React.FC = () => {
</span>
</div>
)}
<h2 className="text-2xl sm:text-3xl font-serif font-bold text-white mb-4 group-hover:text-[var(--luxury-gold)] transition-colors duration-500 line-clamp-2 leading-tight tracking-tight">
<h2 className={`text-2xl sm:text-3xl font-serif font-bold ${textClasses.primary} mb-4 group-hover:text-[var(--luxury-gold)] transition-colors duration-500 line-clamp-2 leading-tight tracking-tight`}>
{service.title}
</h2>
{service.description && (
<p className="text-gray-300 text-base mb-6 line-clamp-3 font-light leading-relaxed">
<p className={`${textClasses.secondary} text-base mb-6 line-clamp-3 font-light leading-relaxed`}>
{service.description}
</p>
)}
@@ -360,7 +394,7 @@ const ServicesPage: React.FC = () => {
{formatCurrency(service.price)}
</span>
{service.unit && (
<span className="text-sm text-gray-400 font-light">
<span className={`text-sm ${textClasses.muted} font-light`}>
/ {service.unit}
</span>
)}