feat: complete website implementation
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Product } from '@/types/types';
|
||||
import { formatCurrency } from '@/lib/utils';
|
||||
import { useCartStore } from '@/lib/cart-store';
|
||||
import { ShoppingCart, Heart, Scale, ShieldCheck, Truck, Clock, AlertCircle, Wrench, ChevronDown, Check, Zap, Volume2, ThermometerSun, Settings, ArrowRightLeft, Sun, Mail, Info, Snowflake, CreditCard } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
|
||||
interface ProductInfoProps {
|
||||
product: Product;
|
||||
}
|
||||
|
||||
export function ProductInfo({ product }: ProductInfoProps) {
|
||||
const router = useRouter();
|
||||
const addItem = useCartStore((state) => state.addItem);
|
||||
|
||||
// Wariant
|
||||
const hasVariants = product.variants && product.variants.length > 0;
|
||||
const defaultVariant = hasVariants ? product.variants![0] : null;
|
||||
const [selectedVariant, setSelectedVariant] = useState(defaultVariant);
|
||||
const [hoursPerDay, setHoursPerDay] = useState<number>(1);
|
||||
|
||||
// Aktualnie wybrana cena
|
||||
const currentPrice = selectedVariant ? selectedVariant.price : product.price;
|
||||
const currentPriceWithInstall = selectedVariant ? selectedVariant.priceWithInstall : product.priceWithInstall;
|
||||
const currentPower = selectedVariant ? selectedVariant.power : product.power;
|
||||
const currentLabel = selectedVariant ? selectedVariant.label : undefined;
|
||||
|
||||
const [addedMessage, setAddedMessage] = useState('');
|
||||
|
||||
// Kalkulator energii
|
||||
const costPerKwh = 1.15; // Przykładowy koszt 1 kWh w PLN
|
||||
const consumptionCooling = currentPower / 4;
|
||||
const consumptionHeating = currentPower / 3.8;
|
||||
const costHourCooling = consumptionCooling * costPerKwh;
|
||||
const costHourHeating = consumptionHeating * costPerKwh;
|
||||
|
||||
const monthlyCostCooling = costHourCooling * hoursPerDay * 30;
|
||||
const monthlyCostHeating = costHourHeating * hoursPerDay * 30;
|
||||
|
||||
const handleAddToCart = (withInstall: boolean) => {
|
||||
addItem({
|
||||
slug: product.slug,
|
||||
name: product.name,
|
||||
brand: product.brand,
|
||||
image: product.image || '/hero.jpg',
|
||||
power: currentPower,
|
||||
label: currentLabel,
|
||||
price: currentPrice,
|
||||
priceWithInstall: currentPriceWithInstall || currentPrice,
|
||||
withInstall
|
||||
});
|
||||
|
||||
setAddedMessage(withInstall ? 'Dodano z montażem' : 'Dodano do koszyka');
|
||||
setTimeout(() => setAddedMessage(''), 3000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex justify-between items-start mb-2">
|
||||
<span className="text-sm font-bold text-text-muted uppercase tracking-wider">{product.brand}</span>
|
||||
<div className="flex items-center gap-2 text-text-muted">
|
||||
<button className="btn btn-ghost btn-circle btn-sm hover:text-accent" aria-label="Dodaj do listy życzeń">
|
||||
<Heart className="w-5 h-5" />
|
||||
</button>
|
||||
<button className="btn btn-ghost btn-circle btn-sm hover:text-accent" aria-label="Porównaj">
|
||||
<ArrowRightLeft className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 className="text-3xl md:text-4xl font-extrabold text-primary font-heading leading-tight mb-4">
|
||||
{product.name}
|
||||
</h1>
|
||||
|
||||
{/* Cechy skrócone */}
|
||||
{product.features && (
|
||||
<div className="flex flex-wrap gap-2 mb-8">
|
||||
{product.features.map((feat, idx) => (
|
||||
<span key={idx} className="bg-slate-100 text-text-muted text-xs font-semibold px-2.5 py-1 rounded-md border border-border">
|
||||
{feat}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Wybór Wariantu */}
|
||||
{hasVariants && (
|
||||
<div className="mb-6">
|
||||
<span className="block text-xs font-semibold text-text-muted uppercase tracking-wider mb-3">Wybierz wariant mocy:</span>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{product.variants!.map((variant, idx) => (
|
||||
<button
|
||||
key={idx}
|
||||
onClick={() => setSelectedVariant(variant)}
|
||||
className={`px-4 py-2 rounded-lg border text-sm font-bold transition-all ${
|
||||
selectedVariant?.label === variant.label
|
||||
? 'border-primary bg-primary text-white shadow-md'
|
||||
: 'border-border bg-white text-text-muted hover:border-primary/50 hover:text-primary'
|
||||
}`}
|
||||
>
|
||||
{variant.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Szybka specyfikacja z ikonami */}
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4 mb-6 text-sm">
|
||||
<div className="flex items-center gap-2 text-primary font-semibold">
|
||||
<Snowflake className="w-4 h-4 text-accent" />
|
||||
<span>{product.specs?.["Klasa energetyczna chłodzenie/grzanie"]?.split("/")[0]?.trim() || "A++"}</span>
|
||||
</div>
|
||||
{product.heating !== false && (
|
||||
<div className="flex items-center gap-2 text-primary font-semibold">
|
||||
<Sun className="w-4 h-4 text-orange-500" />
|
||||
<span>{product.specs?.["Klasa energetyczna chłodzenie/grzanie"]?.split("/")[1]?.trim() || "A+"}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-2 text-primary font-semibold">
|
||||
<Zap className="w-4 h-4 text-yellow-500" />
|
||||
<span>{(currentPower / 4).toFixed(2)} kW/h</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-primary font-semibold">
|
||||
<Zap className="w-4 h-4 text-yellow-500" />
|
||||
<span>{currentPower} kW</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Szczegółowa specyfikacja skrócona (lista) */}
|
||||
<ul className="space-y-2.5 mb-8 text-sm text-text-muted">
|
||||
<li className="flex gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-accent mt-2 shrink-0"></span>
|
||||
<span><strong className="text-primary font-semibold">Wydajność chłodnicza:</strong> {(currentPower).toFixed(2)} kW</span>
|
||||
</li>
|
||||
{product.heating !== false && (
|
||||
<li className="flex gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-accent mt-2 shrink-0"></span>
|
||||
<span><strong className="text-primary font-semibold">Wydajność grzewcza:</strong> {(currentPower * 1.1).toFixed(2)} kW</span>
|
||||
</li>
|
||||
)}
|
||||
<li className="flex gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-accent mt-2 shrink-0"></span>
|
||||
<span><strong className="text-primary font-semibold">Średnie zużycie energii (chłodzenie/grzanie):</strong> {(consumptionCooling).toFixed(2)} / {(consumptionHeating).toFixed(2)} kW</span>
|
||||
</li>
|
||||
<li className="flex gap-2 flex-col">
|
||||
<div className="flex gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-accent mt-2 shrink-0"></span>
|
||||
<span><strong className="text-primary font-semibold">Szacunkowy koszt energii – 1 h (chłodzenie/grzanie):</strong> {costHourCooling.toFixed(2)} / {costHourHeating.toFixed(2)} zł</span>
|
||||
</div>
|
||||
<div className="ml-3.5 mt-2 p-4 bg-white rounded-xl border border-border shadow-sm inline-block w-full max-w-sm">
|
||||
<div className="flex items-center justify-between gap-4 mb-3">
|
||||
<span className="text-sm font-medium text-text-muted">Ile godzin dziennie używasz?</span>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
max="24"
|
||||
value={hoursPerDay}
|
||||
onChange={(e) => setHoursPerDay(Number(e.target.value) || 1)}
|
||||
className="w-16 px-2 py-1.5 text-sm font-bold text-center border border-border rounded-lg outline-none focus:border-primary transition-colors"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-xs text-text-muted leading-relaxed">
|
||||
Twój miesięczny koszt wyniesie: <br />
|
||||
<strong className="text-primary font-bold">Chłodzenie {monthlyCostCooling.toFixed(2)} zł</strong> / <strong className="text-primary font-bold">Grzanie {monthlyCostHeating.toFixed(2)} zł</strong>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li className="flex gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-accent mt-2 shrink-0"></span>
|
||||
<span><strong className="text-primary font-semibold">SEER / SCOP:</strong> {product.specs?.["SEER / SCOP"] || "6.10 / 4.00"}</span>
|
||||
</li>
|
||||
<li className="flex gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-accent mt-2 shrink-0"></span>
|
||||
<span><strong className="text-primary font-semibold">Klasa energetyczna (chłodzenie/grzanie):</strong> {product.specs?.["Klasa energetyczna chłodzenie/grzanie"] || "A++ / A+"}</span>
|
||||
</li>
|
||||
<li className="flex gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-accent mt-2 shrink-0"></span>
|
||||
<span><strong className="text-primary font-semibold">Poziom głośności jednostki wewnętrznej:</strong> {product.specs?.["Poziom hałasu min/max (dB)"] || "24–38 dB"}</span>
|
||||
</li>
|
||||
<li className="flex gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-accent mt-2 shrink-0"></span>
|
||||
<span><strong className="text-primary font-semibold">Zakres pracy - chłodzenie:</strong> {product.specs?.["Zakres temperatur zewnętrznych - chłodzenie"] || "-15~43 °C"}</span>
|
||||
</li>
|
||||
<li className="flex gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-accent mt-2 shrink-0"></span>
|
||||
<span><strong className="text-primary font-semibold">Czas gwarancji:</strong> 5 lat</span>
|
||||
</li>
|
||||
{product.features && (
|
||||
<li className="flex gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-accent mt-2 shrink-0"></span>
|
||||
<span><strong className="text-primary font-semibold">Funkcje:</strong> {product.features.join(", ")}</span>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
|
||||
{/* Dostępność i Kontakt */}
|
||||
<div className="flex items-center gap-6 py-4 border-y border-border mb-8">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3.5 h-3.5 rounded-full bg-green-500 shadow-sm shadow-green-200"></div>
|
||||
<span className="text-sm font-medium text-primary">Dostępny od ręki</span>
|
||||
</div>
|
||||
<Link href="/kontakt" className="flex items-center gap-2 text-text-muted hover:text-primary transition-colors">
|
||||
<Mail className="w-4 h-4" />
|
||||
<span className="text-sm">Zapytaj o produkt</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Ceny i CTA */}
|
||||
<div className="bg-slate-50 p-6 rounded-2xl border border-border mb-8 space-y-6">
|
||||
|
||||
{/* Cena z montażem (Promowana) */}
|
||||
{currentPriceWithInstall && (
|
||||
<div className="flex flex-col gap-3 pb-6 border-b border-border/50">
|
||||
<div className="flex justify-between items-end flex-wrap gap-4">
|
||||
<div>
|
||||
<span className="flex items-center gap-1 text-text-muted text-xs font-semibold mb-1 uppercase tracking-wider">
|
||||
Cena z montażem dla konsumenta (8% VAT) <Info className="w-3 h-3" />
|
||||
</span>
|
||||
<div className="flex items-baseline gap-2">
|
||||
<span className="text-3xl font-extrabold text-primary font-heading">{formatCurrency(currentPriceWithInstall)}</span>
|
||||
</div>
|
||||
<div className="text-xs text-accent font-medium mt-1">Darmowa wycena</div>
|
||||
</div>
|
||||
<button onClick={() => handleAddToCart(true)} className="btn btn-primary text-white">
|
||||
<ShoppingCart className="w-4 h-4 mr-2" />
|
||||
Kup z montażem
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Cena bez montażu */}
|
||||
<div className="flex justify-between items-end flex-wrap gap-4">
|
||||
<div>
|
||||
<span className="block text-text-muted text-xs font-semibold mb-1 uppercase tracking-wider">Cena urządzenia bez montażu (23% VAT)</span>
|
||||
<div className="text-xl font-bold text-primary font-heading">{formatCurrency(currentPrice)}</div>
|
||||
</div>
|
||||
<button onClick={() => handleAddToCart(false)} className="btn btn-outline btn-primary bg-white">
|
||||
Do koszyka
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Feedback o dodaniu */}
|
||||
{addedMessage && (
|
||||
<div className="flex items-center gap-2 text-sm font-bold text-green-600 bg-green-50 p-3 rounded-lg border border-green-200 animate-in fade-in slide-in-from-top-2">
|
||||
<Check className="w-4 h-4" />
|
||||
{addedMessage}. <button onClick={() => router.push('/koszyk')} className="underline hover:text-green-800">Przejdź do koszyka</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Atuty */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||
<div className="flex flex-col items-center text-center p-4 bg-white rounded-xl border border-border">
|
||||
<Truck className="w-6 h-6 text-accent mb-2" />
|
||||
<span className="text-xs font-bold text-primary">Darmowa dostawa</span>
|
||||
<span className="text-[10px] text-text-muted">na terenie Polski</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center text-center p-4 bg-white rounded-xl border border-border">
|
||||
<ShieldCheck className="w-6 h-6 text-accent mb-2" />
|
||||
<span className="text-xs font-bold text-primary">5 lat gwarancji</span>
|
||||
<span className="text-[10px] text-text-muted">na urządzenie i montaż</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center text-center p-4 bg-white rounded-xl border border-border">
|
||||
<Settings className="w-6 h-6 text-accent mb-2" />
|
||||
<span className="text-xs font-bold text-primary">Pełne wsparcie</span>
|
||||
<span className="text-[10px] text-text-muted">na każdym etapie</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user