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