shop.astro raw

   1  ---
   2  export const prerender = false;
   3  import Base from '../layouts/Base.astro';
   4  import { getAllProducts, getProductsByCategory, getCategories, firstImage } from '../lib/products';
   5  
   6  const cat = Astro.url.searchParams.get('cat');
   7  const products = cat ? getProductsByCategory(cat) : getAllProducts();
   8  const categories = getCategories();
   9  ---
  10  
  11  <Base title="Магазин" titleEn="Shop">
  12    <main class="shop-page">
  13      <section class="about-hero">
  14        <div class="about-hero-bg" id="shop-hero-bg" style="background-image: url('/images/shop-hero.jpg')"></div>
  15        <div class="container about-hero-content">
  16          <h1 class="about-hero-title" style="color:var(--white)">МАГАЗИН</h1>
  17        </div>
  18      </section>
  19  
  20      <section class="shop-content section">
  21        <div class="container">
  22          <div class="shop-layout">
  23            <aside class="shop-sidebar">
  24              <h3 data-i18n="categories">Категории</h3>
  25              <ul class="category-filter">
  26                <li><a href="/shop" class={!cat ? 'active' : ''} data-cat="all" data-i18n="allProducts">Всички продукти</a></li>
  27                {categories.filter(c => c.category !== 'smest').map(c => {
  28                  const catKey = c.category === 'bilki' ? 'herbs' : 'fruits';
  29                  const catLabel = c.category === 'bilki' ? 'Билки' : 'Диви плодове';
  30                  return (
  31                  <li><a href={`/shop?cat=${c.category}`} class={cat === c.category ? 'active' : ''} data-cat={c.category} data-i18n={catKey}>{catLabel}</a></li>
  32                  );
  33                })}
  34              </ul>
  35            </aside>
  36  
  37            <div class="product-grid" id="product-grid">
  38              {products.map(p => (
  39                <a href={`/product/${p.slug}`} class="product-card" data-category={p.category}>
  40                  <div class="product-image">
  41                    <img src={firstImage(p.images)} alt={p.name} loading="lazy" />
  42                    {p.stock_count < 1 && (
  43                      <span class="sold-out-badge" data-i18n="soldOut">Изчерпан</span>
  44                    )}
  45                  </div>
  46                  <div class="product-info">
  47                    <h3 class="product-name" data-i18n-base={p.name}>{p.name}</h3>
  48                    <p class="product-name-en">{p.name_en}</p>
  49                    <p class="product-price">{(p.price_cents / 100).toFixed(2)} EUR</p>
  50                    <p class="product-weight"><span data-i18n="productWeight">Грамаж</span>: {p.unit_size} <span data-i18n="unitG">{p.unit}</span></p>
  51                    {p.harvest_year && (
  52                      <p class="product-harvest-card"><span data-i18n="productHarvest">Реколта</span>: {p.harvest_year}</p>
  53                    )}
  54                    <p class="product-stock-card"><span data-i18n="productStock">Наличност</span>: {p.stock_count} <span data-i18n="productBroy">бр.</span></p>
  55                    <button class="btn btn-sm add-to-cart" data-slug={p.slug} data-name={p.name} data-name-en={p.name_en} data-price={p.price_cents} data-image={p.images} data-stock={p.stock_count} data-unit-size={p.unit_size} data-unit={p.unit} disabled={p.stock_count < 1}>
  56                      <span data-i18n={p.stock_count < 1 ? 'soldOut' : 'addToCart'}>{p.stock_count < 1 ? 'Изчерпан' : 'Добави в количка'}</span>
  57                    </button>
  58                  </div>
  59                </a>
  60              ))}
  61            </div>
  62          </div>
  63        </div>
  64      </section>
  65    </main>
  66  </Base>
  67  
  68  <script>
  69  import { addToCart, getCart } from '../lib/cart-client';
  70  import { t } from '../lib/i18n';
  71  
  72  // Category filter
  73  const urlParams = new URLSearchParams(window.location.search);
  74  const activeCat = urlParams.get('cat') || 'all';
  75  document.querySelectorAll('.category-filter a').forEach(a => {
  76    a.classList.toggle('active', (a as HTMLElement).dataset.cat === activeCat);
  77  });
  78  
  79  function updateButtonStates() {
  80    const cart = getCart();
  81    document.querySelectorAll('.add-to-cart').forEach(el => {
  82      const btn = el as HTMLButtonElement;
  83      const slug = btn.dataset.slug!;
  84      const stock = parseInt(btn.dataset.stock!) || 0;
  85      if (stock < 1) return;
  86      const inCart = cart.find(it => it.slug === slug);
  87      if (inCart && inCart.qty >= stock) {
  88        btn.disabled = true;
  89        btn.innerHTML = `<span data-i18n="noMoreAvailable">${t('noMoreAvailable')}</span>`;
  90      } else {
  91        btn.disabled = false;
  92        btn.innerHTML = `<span data-i18n="addToCart">${t('addToCart')}</span>`;
  93      }
  94    });
  95  }
  96  
  97  updateButtonStates();
  98  window.addEventListener('cart-updated', updateButtonStates);
  99  
 100  // Add to cart
 101  document.querySelectorAll('.add-to-cart').forEach(el => {
 102    const btn = el as HTMLButtonElement;
 103    btn.addEventListener('click', (e) => {
 104      e.preventDefault();
 105      e.stopPropagation();
 106      addToCart({
 107        slug: btn.dataset.slug!,
 108        name: btn.dataset.name!,
 109        name_en: btn.dataset.nameEn || btn.dataset.name!,
 110        price_cents: parseInt(btn.dataset.price!),
 111        image: btn.dataset.image!,
 112        unit_size: parseInt(btn.dataset.unitSize!) || 100,
 113        unit: btn.dataset.unit! || 'g',
 114        stock: parseInt(btn.dataset.stock!) || 0,
 115      }, 1);
 116      btn.textContent = '✓';
 117      setTimeout(() => { updateButtonStates(); }, 1000);
 118    });
 119  });
 120  </script>
 121  
 122  <style>
 123    .shop-layout {
 124      display: grid;
 125      grid-template-columns: 250px 1fr;
 126      gap: 2rem;
 127    }
 128    .shop-sidebar h3 {
 129      margin-bottom: 1rem;
 130      font-family: 'Roboto Condensed', sans-serif;
 131    }
 132    .category-filter {
 133      list-style: none;
 134      padding: 0;
 135    }
 136    .category-filter li {
 137      margin-bottom: 0.5rem;
 138    }
 139    .category-filter a {
 140      display: block;
 141      padding: 0.5rem 1rem;
 142      border-radius: 8px;
 143      color: var(--text);
 144      text-decoration: none;
 145      transition: background 0.2s;
 146    }
 147    .category-filter a:hover,
 148    .category-filter a.active {
 149      background: var(--primary);
 150      color: white;
 151    }
 152    .product-grid {
 153      display: grid;
 154      grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
 155      gap: 1.5rem;
 156    }
 157    .product-card {
 158      background: white;
 159      border-radius: 12px;
 160      overflow: hidden;
 161      box-shadow: 0 2px 8px rgba(0,0,0,0.08);
 162      text-decoration: none;
 163      color: var(--text);
 164      transition: transform 0.2s, box-shadow 0.2s;
 165    }
 166    .product-card:hover {
 167      transform: translateY(-4px);
 168      box-shadow: 0 8px 24px rgba(0,0,0,0.12);
 169    }
 170    .product-image {
 171      width: 100%;
 172      height: 200px;
 173      overflow: hidden;
 174      position: relative;
 175    }
 176    .product-image img {
 177      width: 100%;
 178      height: 100%;
 179      object-fit: cover;
 180    }
 181    .sold-out-badge {
 182      position: absolute;
 183      top: 8px;
 184      right: 8px;
 185      background: #c0392b;
 186      color: #fff;
 187      font-size: 0.7rem;
 188      font-weight: 700;
 189      text-transform: uppercase;
 190      padding: 3px 8px;
 191      border-radius: 4px;
 192      letter-spacing: 0.5px;
 193      z-index: 1;
 194    }
 195    .product-info {
 196      padding: 1rem;
 197    }
 198    .product-name {
 199      font-family: 'Roboto Condensed', sans-serif;
 200      font-size: 1.1rem;
 201      margin: 0 0 0.25rem;
 202    }
 203    .product-name-en {
 204      font-size: 0.85rem;
 205      color: var(--text-light);
 206      margin: 0 0 0.5rem;
 207      font-style: italic;
 208    }
 209    .product-price {
 210      font-weight: 600;
 211      color: var(--primary);
 212      margin: 0;
 213    }
 214    .product-weight {
 215      font-size: 0.8rem;
 216      color: var(--text-light);
 217      margin: 0 0 0.2rem;
 218    }
 219    .product-harvest-card {
 220      font-size: 0.8rem;
 221      color: var(--text-light);
 222      margin: 0 0 0.2rem;
 223    }
 224    .product-stock-card {
 225      font-size: 0.8rem;
 226      color: var(--text-light);
 227      margin: 0 0 0.5rem;
 228    }
 229    .add-to-cart {
 230      width: 100%;
 231      background: var(--primary);
 232      color: #fff;
 233      border: none;
 234      -webkit-appearance: none;
 235      appearance: none;
 236    }
 237    .add-to-cart:hover {
 238      background: var(--primary-hover);
 239    }
 240    .add-to-cart:disabled { opacity: 0.45; cursor: default; }
 241    @media (max-width: 768px) {
 242      .shop-layout {
 243        grid-template-columns: 1fr;
 244      }
 245      .shop-sidebar {
 246        display: flex;
 247        flex-wrap: wrap;
 248        gap: 0.5rem;
 249      }
 250      .shop-sidebar h3 {
 251        width: 100%;
 252      }
 253      .category-filter {
 254        display: flex;
 255        flex-wrap: wrap;
 256        gap: 0.5rem;
 257      }
 258      .category-filter li {
 259        margin-bottom: 0;
 260      }
 261    }
 262  </style>
 263  
 264  <script>
 265    // parallax hero for shop page
 266    const shopBg = document.getElementById('shop-hero-bg');
 267    if (shopBg) {
 268      const factor = 0.5;
 269      function parallax() {
 270        shopBg.style.transform = `translateY(${window.scrollY * factor}px)`;
 271      }
 272      window.addEventListener('scroll', parallax, { passive: true });
 273      parallax();
 274    }
 275  </script>
 276