// @ts-nocheck
'use client';
import { useState, useEffect, useMemo, Suspense } from 'react';
import { motion } from 'framer-motion';
import { createClient } from '@/utils/supabase/client';
import { Deal, Tag } from '@/types/types';
import { IconType } from 'react-icons';
import { MdLocalOffer } from 'react-icons/md';
import { BsFillCalendarEventFill } from 'react-icons/bs';
import { GiPartyPopper, GiChessKing, GiCardAceHearts } from 'react-icons/gi';
import { BiSolidDrink } from 'react-icons/bi';
import { FaWineGlassAlt, FaMoneyBillWave } from 'react-icons/fa';
import { FaCrown, FaGlassMartiniAlt, FaUserTie } from 'react-icons/fa';
import * as Md from 'react-icons/md';
import * as Fa from 'react-icons/fa';
import * as Fa6 from 'react-icons/fa6';
import * as Fi from 'react-icons/fi';
import * as Gi from 'react-icons/gi';
import * as Bi from 'react-icons/bi';
import * as Bs from 'react-icons/bs';
import * as Go from 'react-icons/go';
import * as Hi from 'react-icons/hi';
import * as Hi2 from 'react-icons/hi2';
import * as Ai from 'react-icons/ai';
// Animation variants
const fadeIn = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0, transition: { duration: 0.5, ease: "easeOut" } }
};
const staggerContainer = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1
}
}
};
// Map of icon libraries
const iconLibraries = {
Md, Fa, Fa6, Fi, Gi, Bi, Bs, Go, Hi, Hi2, Ai
};
// Icon mapping helper - maps icon string names to actual React components
const iconMap: Record<string, IconType> = {
MdLocalOffer,
BsFillCalendarEventFill,
GiPartyPopper,
GiChessKing,
GiCardAceHearts,
BiSolidDrink,
FaWineGlassAlt,
FaMoneyBillWave,
FaCrown,
FaGlassMartiniAlt,
FaUserTie
};
// Component to render a package card with tags
const PackageCard = ({ pack, tags = [] }) => {
// Extract classes from icon string if present
let iconComponent = <MdLocalOffer className="text-xl text-orange-500" />;
if (pack.icon) {
const iconParts = pack.icon.split(' ');
const iconName = iconParts[0];
// Use a smaller icon size
const iconClasses = (iconParts.slice(1).join(' ') || "text-4xl text-orange-500").replace("text-4xl", "text-xl");
// Check if it's directly in our iconMap
if (iconMap[iconName]) {
const IconComponent = iconMap[iconName];
iconComponent = <IconComponent className={iconClasses} />;
}
// Try to find the icon in imported libraries
else {
// Match the first 2 characters to find the library (e.g., Fa, Md, Bi, etc.)
const prefix = iconName.match(/^[A-Z][a-z]/)?.[0];
if (prefix) {
const libraryName = prefix === 'Fa' && iconName.includes('6') ? 'Fa6' : prefix;
const library = iconLibraries[libraryName];
if (library && library[iconName]) {
const IconComponent = library[iconName];
iconComponent = <IconComponent className={iconClasses} />;
}
}
}
}
return (
<motion.div
variants={fadeIn}
className="bg-white rounded-xl shadow-md overflow-hidden transform transition-all duration-300 hover:shadow-lg border border-gray-100"
>
<div className="p-5 flex justify-between items-center">
<h3 className="text-xl font-bold tracking-tight">{pack.title}</h3>
<div className="flex items-center bg-orange-100 rounded-full overflow-hidden shadow-sm">
<div className="px-2 py-1">
{iconComponent}
</div>
<div className="px-3 py-1 bg-orange-500 text-white text-sm font-medium flex items-center gap-2">
{pack.regular_price && (
<span className="text-white/70 line-through text-xs">{pack.regular_price} лв</span>
)}
<span>{pack.price} лв</span>
</div>
</div>
</div>
<div className="px-5 pb-5">
<p className="text-gray-600 mb-4 text-sm">{pack.description}</p>
{/* Tags */}
{tags.length > 0 && (
<div className="flex flex-wrap gap-1 mb-4">
{tags.map(tag => (
<span key={tag.id} className="border border-gray-200 text-gray-600 text-xs px-2 py-0.5 rounded-full">
{tag.name}
</span>
))}
</div>
)}
<h4 className="font-semibold text-gray-800 mb-2 text-sm">Включва:</h4>
<ul className="space-y-2 mb-4 text-sm">
{pack.benefits?.map((item, index) => (
<li key={index} className="flex items-start">
<span className="text-orange-500 mr-2">✓</span>
<span className="text-gray-700">{item}</span>
</li>
))}
</ul>
{pack.for_who && (
<div className="mt-3 text-xs bg-gray-50 p-2 rounded-lg text-gray-600">
<strong>Идеален за:</strong> {pack.for_who}
</div>
)}
</div>
</motion.div>
);
};
// Component to render a section of packages
const Section = ({ title, packages, color }) => {
if (!packages || packages.length === 0) return null;
return (
<div className="mb-16">
<h2 className="text-2xl font-bold mb-8 text-gray-800 flex items-center">
<span className="mr-2">{title.split(' ')[0]}</span>
<span className="text-orange-600">{title.split(' ').slice(1).join(' ')}</span>
</h2>
<motion.div
variants={staggerContainer}
initial="hidden"
animate="visible"
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"
>
{packages.map((pack, index) => (
<PackageCard key={index} pack={pack} color={color} />
))}
</motion.div>
</div>
);
};
function DealsContent() {
const [deals, setDeals] = useState<Deal[]>([]);
const [tags, setTags] = useState<Tag[]>([]);
const [dealTags, setDealTags] = useState<Record<number, Tag[]>>({});
const [selectedTagIds, setSelectedTagIds] = useState<number[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [showMoreTags, setShowMoreTags] = useState(false);
const maxTagsToShow = 6;
useEffect(() => {
const fetchData = async () => {
try {
setIsLoading(true);
const supabase = createClient();
// Fetch deals
const { data: dealsData, error: dealsError } = await supabase
.from('deals')
.select('*');
if (dealsError) throw dealsError;
setDeals(dealsData || []);
// Fetch tags
const { data: tagsData, error: tagsError } = await supabase
.from('tags')
.select('*');
if (tagsError) throw tagsError;
setTags(tagsData || []);
// Fetch deal-tag relationships
const { data: dealTagsData, error: dealTagsError } = await supabase
.from('deal_tags')
.select('*');
if (dealTagsError) throw dealTagsError;
// Build map of deal_id -> tags[]
const tagMap: Record<number, Tag[]> = {};
if (dealTagsData) {
for (const dt of dealTagsData) {
const tag = tagsData?.find(t => t.id === dt.tag_id);
if (tag) {
if (!tagMap[dt.deal_id]) tagMap[dt.deal_id] = [];
tagMap[dt.deal_id].push(tag);
}
}
}
setDealTags(tagMap);
} catch (err) {
console.error('Error fetching data:', err);
setError('Failed to load deals. Please try again later.');
} finally {
setIsLoading(false);
}
};
fetchData();
}, []);
// Count tag occurrences for popularity sorting
const tagCounts = useMemo(() => {
const counts: Record<number, number> = {};
Object.values(dealTags).forEach(tags => {
tags.forEach(tag => {
counts[tag.id] = (counts[tag.id] || 0) + 1;
});
});
return counts;
}, [dealTags]);
// Sort tags by frequency (most used first)
const sortedTags = useMemo(() => {
return [...tags].sort((a, b) => (tagCounts[b.id] || 0) - (tagCounts[a.id] || 0));
}, [tags, tagCounts]);
// Filter deals by selected tags
const filteredDeals = useMemo(() => {
if (selectedTagIds.length === 0) return deals.sort((a, b) => a.price - b.price);
return deals.filter(deal => {
const dealTagsList = dealTags[deal.id] || [];
return selectedTagIds.some(tagId =>
dealTagsList.some(tag => tag.id === tagId)
);
}).sort((a, b) => a.price - b.price);
}, [deals, dealTags, selectedTagIds]);
const handleTagClick = (tagId: number) => {
setSelectedTagIds(prev => {
if (prev.includes(tagId)) {
return prev.filter(id => id !== tagId);
} else {
return [...prev, tagId];
}
});
};
// Get tags to display based on showMoreTags state
const displayedTags = showMoreTags
? sortedTags
: sortedTags.slice(0, maxTagsToShow);
if (isLoading) {
return (
<div className="max-w-6xl mx-auto px-4 py-12">
{/* Skeleton for the title - matching the real title's styling */}
<div className="animate-pulse">
<div className="h-12 bg-gray-200 rounded w-64 mx-auto mb-10"></div>
{/* Skeleton for the tags section */}
<div className="mb-8 flex flex-wrap gap-2 justify-center">
{[1, 2, 3, 4, 5, 6].map(i => (
<div key={i} className="w-20 h-8 bg-gray-200 rounded-full"></div>
))}
</div>
{/* Skeleton for the deals grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{[1, 2, 3, 4, 5, 6].map(i => (
<div key={i} className="bg-gray-100 h-96 rounded-xl shadow-md"></div>
))}
</div>
</div>
</div>
);
}
if (error) {
return (
<div className="max-w-6xl mx-auto px-4 py-16 text-center">
<div className="bg-red-100 p-6 rounded-xl shadow-sm">
<h2 className="text-2xl font-bold text-red-700 mb-4">Oops!</h2>
<p className="text-red-600">{error}</p>
</div>
</div>
);
}
return (
<div className="max-w-6xl mx-auto px-4 py-12">
<motion.h1
className="text-3xl font-bold text-center mb-10"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
Специални предложения
</motion.h1>
{/* Tags filter section */}
{sortedTags.length > 0 && (
<div className="mb-8">
<div className="flex flex-wrap gap-2 justify-center">
{displayedTags.map(tag => (
<button
key={tag.id}
onClick={() => handleTagClick(tag.id)}
className={`px-3 py-1 rounded-full text-sm transition-all ${
selectedTagIds.includes(tag.id)
? 'bg-orange-500 text-white'
: 'border border-gray-200 text-gray-700 hover:bg-gray-50'
}`}
>
{tag.name}
{tagCounts[tag.id] > 1 && (
<span className="ml-1 text-xs">({tagCounts[tag.id]})</span>
)}
</button>
))}
{/* Show more/less button */}
{sortedTags.length > maxTagsToShow && (
<button
onClick={() => setShowMoreTags(!showMoreTags)}
className="px-3 py-1 rounded-full text-sm border border-gray-200 text-gray-700 hover:bg-gray-50"
>
{showMoreTags ? 'Покажи по-малко' : 'Покажи повече'}
</button>
)}
</div>
</div>
)}
{/* All deals in grid */}
<motion.div
variants={staggerContainer}
initial="hidden"
animate="visible"
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
>
{filteredDeals.map((deal, index) => (
<PackageCard
key={deal.id}
pack={deal}
tags={dealTags[deal.id] || []}
/>
))}
</motion.div>
{filteredDeals.length === 0 && (
<div className="text-center py-10">
<p className="text-gray-500">Няма съвпадения с избраните филтри.</p>
{selectedTagIds.length > 0 && (
<button
onClick={() => setSelectedTagIds([])}
className="mt-4 px-4 py-2 bg-orange-500 text-white rounded-lg"
>
Изчисти филтрите
</button>
)}
</div>
)}
<div className="mt-16 p-8 bg-gradient-to-r from-orange-500 to-orange-600 rounded-xl shadow-lg text-center text-white">
<h3 className="text-2xl font-bold mb-4">
Искате персонализирано предложение?
</h3>
<p className="mb-6 text-white/90">
Свържете се с нас за да обсъдим вашите нужди и да създадем специално
предложение за вас.
</p>
<a
href="tel:+359886644460"
className="inline-block bg-white text-orange-600 px-6 py-3 rounded-full font-semibold shadow-md hover:shadow-lg transition-all duration-300"
>
Обади се сега
</a>
</div>
</div>
);
}
export default function Deals() {
return (
<Suspense fallback={<div className="max-w-6xl mx-auto px-4 py-12 text-center">Зареждане...</div>}>
<DealsContent />
</Suspense>
);
}