vkashti / app / deals / page.tsx
page.tsx
Raw
// @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>
  );
}