All files / src/components/features/promotions/ProductPromotion index.tsx

14.4% Statements 18/125
100% Branches 0/0
0% Functions 0/1
14.4% Lines 18/125

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 1261x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x                                                                                                                                                                                                                       1x 1x  
"use client";
 
import { useState, useEffect } from "react";
import { Icon } from "@/components/ui/icons";
import { clientLogger } from '@/lib/logging/clientLogger';
import { SaleBadge } from "../SaleBadge";
import { CountdownTimer } from "../CountdownTimer";
import { cn } from "@/lib/core";
import type { ApplicablePromotion } from "@/lib/promotions/types";
 
interface ProductPromotionProps {
  productId: number;
  originalPrice: number;
  className?: string;
}
 
export function ProductPromotion({
  productId,
  originalPrice,
  className}: ProductPromotionProps) {
  const [promotions, setPromotions] = useState<ApplicablePromotion[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchPromotions() {
      try {
        const response = await fetch(
          `/api/promotions/applicable?productId=${productId}&price=${originalPrice}`
        );
        const data = await response.json();

        if (data.success) {
          setPromotions(data.data || []);
        }
      } catch (error) {
        clientLogger.error('Failed to fetch promotions', error instanceof Error ? error : new Error(String(error)), {
          productId,
          originalPrice
        });
      } finally {
        setLoading(false);
      }
    }

    fetchPromotions();
  }, [productId, originalPrice]);

  if (loading || promotions.length === 0) {
    return null;
  }

  const formatCurrency = (amount: number) => {
    return new Intl.NumberFormat("en-US", {
      style: "currency",
      currency: "USD"}).format(amount);
  };

  // Get the best promotion to display
  const bestPromotion = promotions[0];
  const promotion = bestPromotion.promotion;
  const promoType = promotion.type as string;
  const isFlashSale = promoType === "FLASH_SALE";
  const isBogo = promoType === "BOGO";

  return (
    <div className={cn("space-y-2", className)}>
      {/* Sale Badge */}
      <div className="flex items-center gap-2 flex-wrap">
        {isFlashSale ? (
          <SaleBadge variant="flash" size="sm" />
        ) : isBogo ? (
          <SaleBadge variant="bogo" size="sm" />
        ) : promotion.discountType === "PERCENTAGE" ? (
          <SaleBadge
            variant="sale"
            size="sm"
            discountPercent={promotion.discountValue}
          />
        ) : (
          <SaleBadge
            variant="sale"
            size="sm"
            label={`${formatCurrency(promotion.discountValue)} OFF`}
          />
        )}

        {/* Countdown for Flash Sales */}
        {isFlashSale && promotion.endDate && (
          <CountdownTimer
            endDate={promotion.endDate}
            variant="compact"
            showLabels={false}
          />
        )}
      </div>

      {/* Promotion Details */}
      <div className="text-sm text-gray-600">
        {bestPromotion.message}
      </div>

      {/* Price After Discount */}
      {bestPromotion.discountAmount > 0 && (
        <div className="flex items-center gap-2">
          <span className="text-lg font-bold text-green-600">
            {formatCurrency(originalPrice - bestPromotion.discountAmount)}
          </span>
          <span className="text-sm text-gray-400 line-through">
            {formatCurrency(originalPrice)}
          </span>
        </div>
      )}

      {/* Additional promotions indicator */}
      {promotions.length > 1 && (
        <div className="flex items-center gap-1 text-xs text-blue-600">
          <Icon name="info-circle" size={12} />
          <span>{promotions.length - 1} more offer{promotions.length > 2 ? "s" : ""} available</span>
        </div>
      )}
    </div>
  );
}
 
export default ProductPromotion;