@o2vend/theme-cli 1.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +425 -0
- package/assets/Logo_o2vend.png +0 -0
- package/assets/favicon.png +0 -0
- package/assets/logo-white.png +0 -0
- package/bin/o2vend +42 -0
- package/config/widget-map.json +50 -0
- package/lib/commands/check.js +201 -0
- package/lib/commands/generate.js +33 -0
- package/lib/commands/init.js +214 -0
- package/lib/commands/optimize.js +216 -0
- package/lib/commands/package.js +208 -0
- package/lib/commands/serve.js +105 -0
- package/lib/commands/validate.js +191 -0
- package/lib/lib/api-client.js +357 -0
- package/lib/lib/dev-server.js +2618 -0
- package/lib/lib/file-watcher.js +80 -0
- package/lib/lib/hot-reload.js +106 -0
- package/lib/lib/liquid-engine.js +822 -0
- package/lib/lib/liquid-filters.js +671 -0
- package/lib/lib/mock-api-server.js +989 -0
- package/lib/lib/mock-data.js +1468 -0
- package/lib/lib/widget-service.js +321 -0
- package/package.json +70 -0
- package/test-theme/README.md +27 -0
- package/test-theme/assets/async-sections.js +446 -0
- package/test-theme/assets/cart-drawer.js +463 -0
- package/test-theme/assets/cart-manager.js +223 -0
- package/test-theme/assets/checkout-price-handler.js +368 -0
- package/test-theme/assets/components.css +4629 -0
- package/test-theme/assets/delivery-zone.css +299 -0
- package/test-theme/assets/delivery-zone.js +396 -0
- package/test-theme/assets/logo.png +0 -0
- package/test-theme/assets/sections.css +48 -0
- package/test-theme/assets/theme.css +3500 -0
- package/test-theme/assets/theme.js +3745 -0
- package/test-theme/config/settings_data.json +292 -0
- package/test-theme/config/settings_schema.json +1050 -0
- package/test-theme/layout/theme.liquid +195 -0
- package/test-theme/locales/en.default.json +260 -0
- package/test-theme/sections/content-fallback.liquid +53 -0
- package/test-theme/sections/content.liquid +57 -0
- package/test-theme/sections/footer-fallback.liquid +328 -0
- package/test-theme/sections/footer.liquid +278 -0
- package/test-theme/sections/header-fallback.liquid +1805 -0
- package/test-theme/sections/header.liquid +1145 -0
- package/test-theme/sections/hero-fallback.liquid +212 -0
- package/test-theme/sections/hero.liquid +136 -0
- package/test-theme/snippets/account-sidebar.liquid +200 -0
- package/test-theme/snippets/add-to-cart-modal.liquid +484 -0
- package/test-theme/snippets/breadcrumbs.liquid +134 -0
- package/test-theme/snippets/cart-drawer.liquid +467 -0
- package/test-theme/snippets/delivery-zone-city-selector.liquid +79 -0
- package/test-theme/snippets/delivery-zone-modal.liquid +337 -0
- package/test-theme/snippets/delivery-zone-search.liquid +78 -0
- package/test-theme/snippets/icon.liquid +105 -0
- package/test-theme/snippets/login-modal.liquid +346 -0
- package/test-theme/snippets/mega-menu.liquid +812 -0
- package/test-theme/snippets/news-thumbnail.liquid +187 -0
- package/test-theme/snippets/pagination.liquid +120 -0
- package/test-theme/snippets/price.liquid +92 -0
- package/test-theme/snippets/product-card-related.liquid +78 -0
- package/test-theme/snippets/product-card-simple.liquid +41 -0
- package/test-theme/snippets/product-card.liquid +697 -0
- package/test-theme/snippets/rating.liquid +85 -0
- package/test-theme/snippets/skeleton-collection-grid.liquid +114 -0
- package/test-theme/snippets/skeleton-product-card.liquid +124 -0
- package/test-theme/snippets/skeleton-product-grid.liquid +34 -0
- package/test-theme/snippets/social-sharing.liquid +185 -0
- package/test-theme/templates/account/dashboard.liquid +401 -0
- package/test-theme/templates/account/loyalty-redemption.liquid +405 -0
- package/test-theme/templates/account/loyalty.liquid +588 -0
- package/test-theme/templates/account/order-detail.liquid +230 -0
- package/test-theme/templates/account/orders.liquid +349 -0
- package/test-theme/templates/account/profile.liquid +758 -0
- package/test-theme/templates/account/register.liquid +232 -0
- package/test-theme/templates/account/return-orders.liquid +348 -0
- package/test-theme/templates/account/store-credit.liquid +464 -0
- package/test-theme/templates/account/subscriptions.liquid +601 -0
- package/test-theme/templates/account/wishlist.liquid +419 -0
- package/test-theme/templates/address-book.liquid +1092 -0
- package/test-theme/templates/categories.liquid +452 -0
- package/test-theme/templates/checkout.liquid +4511 -0
- package/test-theme/templates/error.liquid +384 -0
- package/test-theme/templates/index.liquid +11 -0
- package/test-theme/templates/login.liquid +185 -0
- package/test-theme/templates/order-confirmation.liquid +720 -0
- package/test-theme/templates/page.liquid +297 -0
- package/test-theme/templates/product-detail.liquid +4363 -0
- package/test-theme/templates/products.liquid +518 -0
- package/test-theme/templates/search.liquid +922 -0
- package/test-theme/theme.json.example +19 -0
- package/test-theme/widgets/brand-carousel.liquid +676 -0
- package/test-theme/widgets/brand.liquid +245 -0
- package/test-theme/widgets/carousel.liquid +843 -0
- package/test-theme/widgets/category-list-carousel.liquid +656 -0
- package/test-theme/widgets/category-list.liquid +340 -0
- package/test-theme/widgets/category.liquid +475 -0
- package/test-theme/widgets/discount-time.liquid +176 -0
- package/test-theme/widgets/footer-menu.liquid +695 -0
- package/test-theme/widgets/footer.liquid +179 -0
- package/test-theme/widgets/gallery.liquid +271 -0
- package/test-theme/widgets/header-menu.liquid +932 -0
- package/test-theme/widgets/header.liquid +159 -0
- package/test-theme/widgets/html.liquid +214 -0
- package/test-theme/widgets/news.liquid +217 -0
- package/test-theme/widgets/product-canvas.liquid +235 -0
- package/test-theme/widgets/product-carousel.liquid +502 -0
- package/test-theme/widgets/product.liquid +45 -0
- package/test-theme/widgets/recently-viewed.liquid +26 -0
- package/test-theme/widgets/shared/product-grid.liquid +339 -0
- package/test-theme/widgets/simple-product.liquid +42 -0
- package/test-theme/widgets/single-product.liquid +610 -0
- package/test-theme/widgets/spacebar-carousel.liquid +663 -0
- package/test-theme/widgets/spacebar.liquid +279 -0
- package/test-theme/widgets/splash.liquid +378 -0
- package/test-theme/widgets/testimonial-carousel.liquid +709 -0
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
{% layout 'layout/theme' %}
|
|
2
|
+
{% comment %}
|
|
3
|
+
O2VEND Default Theme - Products Template
|
|
4
|
+
{% endcomment %}
|
|
5
|
+
|
|
6
|
+
<!-- Breadcrumb Navigation Removed for Default Style -->
|
|
7
|
+
|
|
8
|
+
<!-- Collection Header -->
|
|
9
|
+
<section class="collection-header">
|
|
10
|
+
<div class="collection-header-wrapper">
|
|
11
|
+
<div class="collection-header-inner">
|
|
12
|
+
<div class="collection-header-left">
|
|
13
|
+
<h1 class="collection-title collection-title--compact">{{ collection.title }}</h1>
|
|
14
|
+
{% if collection.description %}
|
|
15
|
+
<div class="collection-description">
|
|
16
|
+
{{ collection.description }}
|
|
17
|
+
</div>
|
|
18
|
+
{% endif %}
|
|
19
|
+
</div>
|
|
20
|
+
<div class="collection-header-right">
|
|
21
|
+
<span class="collection-count">{{ collection.totalProducts | default: collection.products.size }} products</span>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</section>
|
|
26
|
+
|
|
27
|
+
<!-- Collection Toolbar -->
|
|
28
|
+
<section class="collection-toolbar">
|
|
29
|
+
<div class="container">
|
|
30
|
+
<div class="toolbar-content">
|
|
31
|
+
<!-- Filters -->
|
|
32
|
+
<div class="filters-section">
|
|
33
|
+
<button class="btn btn-outline filter-toggle" id="filter-toggle" aria-label="Filter products">
|
|
34
|
+
<svg class="icon-filter" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
35
|
+
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon>
|
|
36
|
+
</svg>
|
|
37
|
+
<span>Filters</span>
|
|
38
|
+
</button>
|
|
39
|
+
|
|
40
|
+
<div class="filter-dropdown" id="filter-dropdown">
|
|
41
|
+
<div class="filter-header">
|
|
42
|
+
<h3 class="filter-title">Filter Products</h3>
|
|
43
|
+
<button class="filter-close" id="filter-close">×</button>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div class="filter-content">
|
|
47
|
+
<div class="filter-group">
|
|
48
|
+
<h4 class="filter-group-title">PRICE RANGE</h4>
|
|
49
|
+
<div class="price-range">
|
|
50
|
+
<div class="price-inputs">
|
|
51
|
+
<input type="number" id="price-min" placeholder="Min" min="0" class="price-input">
|
|
52
|
+
<span class="price-separator">-</span>
|
|
53
|
+
<input type="number" id="price-max" placeholder="Max" min="0" class="price-input">
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div class="filter-group">
|
|
59
|
+
<h4 class="filter-group-title">AVAILABILITY</h4>
|
|
60
|
+
<div class="filter-options">
|
|
61
|
+
<label class="filter-option">
|
|
62
|
+
<input type="checkbox" name="availability" value="in-stock" class="filter-checkbox">
|
|
63
|
+
<span class="filter-option-text">In Stock</span>
|
|
64
|
+
</label>
|
|
65
|
+
<label class="filter-option">
|
|
66
|
+
<input type="checkbox" name="availability" value="on-sale" class="filter-checkbox">
|
|
67
|
+
<span class="filter-option-text">On Sale</span>
|
|
68
|
+
</label>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<div class="filter-actions">
|
|
74
|
+
<button class="btn btn-ghost" id="clear-filters">Clear All</button>
|
|
75
|
+
<button class="btn btn-primary" id="apply-filters">Apply Filters</button>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<!-- Sort -->
|
|
81
|
+
<div class="sort-section">
|
|
82
|
+
<div class="sort-dropdown" id="sort-dropdown">
|
|
83
|
+
<button class="btn btn-outline sort-toggle" id="sort-toggle" aria-haspopup="listbox" aria-expanded="false" aria-label="Sort products">
|
|
84
|
+
<svg class="icon-sort" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
85
|
+
<path d="M3 6h18M7 12h10M11 18h2"></path>
|
|
86
|
+
</svg>
|
|
87
|
+
<span id="sort-toggle-label">Sort: Featured</span>
|
|
88
|
+
<svg class="icon-chevron" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
89
|
+
<polyline points="6 9 12 15 18 9"></polyline>
|
|
90
|
+
</svg>
|
|
91
|
+
</button>
|
|
92
|
+
<ul class="sort-menu" id="sort-menu" role="listbox">
|
|
93
|
+
<li class="sort-option" role="option" data-value="featured" data-label="Featured">Featured</li>
|
|
94
|
+
<li class="sort-option" role="option" data-value="price-asc" data-label="Price, low to high">Price, low to high</li>
|
|
95
|
+
<li class="sort-option" role="option" data-value="price-desc" data-label="Price, high to low">Price, high to low</li>
|
|
96
|
+
<li class="sort-option" role="option" data-value="newest" data-label="Date, new to old">Date, new to old</li>
|
|
97
|
+
</ul>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</section>
|
|
103
|
+
|
|
104
|
+
<!-- Products Section -->
|
|
105
|
+
{% hook 'product_list_before' %}
|
|
106
|
+
<section class="collection-products">
|
|
107
|
+
<div class="container">
|
|
108
|
+
<!-- Products Grid -->
|
|
109
|
+
<div class="products-container">
|
|
110
|
+
<div class="products-grid" id="products-grid">
|
|
111
|
+
{% for product in collection.products %}
|
|
112
|
+
{% hook 'product_card_before' %}
|
|
113
|
+
{% include 'snippets/product-card', product: product %}
|
|
114
|
+
{% hook 'product_card_after' %}
|
|
115
|
+
{% endfor %}
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<!-- No Products Message -->
|
|
119
|
+
{% if collection.products.size == 0 %}
|
|
120
|
+
<div class="no-products">
|
|
121
|
+
<div class="no-products-content">
|
|
122
|
+
<h3 class="no-products-title">No products found</h3>
|
|
123
|
+
<p class="no-products-description">Try adjusting your filters or browse our other collections.</p>
|
|
124
|
+
<div class="no-products-actions">
|
|
125
|
+
<button class="btn btn-primary" id="clear-all-filters">Clear All Filters</button>
|
|
126
|
+
<a href="/collections" class="btn btn-outline">Browse Collections</a>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
{% endif %}
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<!-- Pagination -->
|
|
134
|
+
{% include 'snippets/pagination', pagination: pagination %}
|
|
135
|
+
</div>
|
|
136
|
+
</section>
|
|
137
|
+
{% hook 'product_list_after' %}
|
|
138
|
+
|
|
139
|
+
<script>
|
|
140
|
+
// Collection page specific JavaScript - Server-side filtering & sorting
|
|
141
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
142
|
+
// Parse URL parameters
|
|
143
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
144
|
+
|
|
145
|
+
// Helper function to build and navigate to filtered URL
|
|
146
|
+
function applyFiltersToURL() {
|
|
147
|
+
const params = new URLSearchParams(window.location.search);
|
|
148
|
+
|
|
149
|
+
// Price filter
|
|
150
|
+
const priceMin = document.getElementById('price-min');
|
|
151
|
+
const priceMax = document.getElementById('price-max');
|
|
152
|
+
|
|
153
|
+
if (priceMin && priceMin.value) {
|
|
154
|
+
params.set('price_min', priceMin.value);
|
|
155
|
+
} else {
|
|
156
|
+
params.delete('price_min');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (priceMax && priceMax.value) {
|
|
160
|
+
params.set('price_max', priceMax.value);
|
|
161
|
+
} else {
|
|
162
|
+
params.delete('price_max');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Availability filter
|
|
166
|
+
const inStockCheckbox = document.querySelector('input[name="availability"][value="in-stock"]');
|
|
167
|
+
if (inStockCheckbox && inStockCheckbox.checked) {
|
|
168
|
+
params.set('in_stock', 'true');
|
|
169
|
+
} else {
|
|
170
|
+
params.delete('in_stock');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const onSaleCheckbox = document.querySelector('input[name="availability"][value="on-sale"]');
|
|
174
|
+
if (onSaleCheckbox && onSaleCheckbox.checked) {
|
|
175
|
+
params.set('on_sale', 'true');
|
|
176
|
+
} else {
|
|
177
|
+
params.delete('on_sale');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Brand filter
|
|
181
|
+
const brandCheckboxes = document.querySelectorAll('input[name="brand"]:checked');
|
|
182
|
+
if (brandCheckboxes.length > 0) {
|
|
183
|
+
const selectedBrands = Array.from(brandCheckboxes).map(cb => cb.value);
|
|
184
|
+
params.set('brand', selectedBrands.join(','));
|
|
185
|
+
} else {
|
|
186
|
+
params.delete('brand');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Reset to page 1 when filters change
|
|
190
|
+
params.set('page', '1');
|
|
191
|
+
|
|
192
|
+
// Navigate to new URL
|
|
193
|
+
window.location.href = `${window.location.pathname}?${params.toString()}`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Initialize filters from URL parameters
|
|
197
|
+
function initializeFiltersFromURL() {
|
|
198
|
+
// Price filters
|
|
199
|
+
const priceMin = document.getElementById('price-min');
|
|
200
|
+
const priceMax = document.getElementById('price-max');
|
|
201
|
+
const priceRangeMin = document.getElementById('price-range-min');
|
|
202
|
+
const priceRangeMax = document.getElementById('price-range-max');
|
|
203
|
+
|
|
204
|
+
if (urlParams.has('price_min')) {
|
|
205
|
+
const minValue = urlParams.get('price_min');
|
|
206
|
+
if (priceMin) priceMin.value = minValue;
|
|
207
|
+
if (priceRangeMin) priceRangeMin.value = minValue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (urlParams.has('price_max')) {
|
|
211
|
+
const maxValue = urlParams.get('price_max');
|
|
212
|
+
if (priceMax) priceMax.value = maxValue;
|
|
213
|
+
if (priceRangeMax) priceRangeMax.value = maxValue;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Availability filters
|
|
217
|
+
const inStockCheckbox = document.querySelector('input[name="availability"][value="in-stock"]');
|
|
218
|
+
if (inStockCheckbox && urlParams.has('in_stock')) {
|
|
219
|
+
inStockCheckbox.checked = true;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const onSaleCheckbox = document.querySelector('input[name="availability"][value="on-sale"]');
|
|
223
|
+
if (onSaleCheckbox && urlParams.has('on_sale')) {
|
|
224
|
+
onSaleCheckbox.checked = true;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Brand filters
|
|
228
|
+
if (urlParams.has('brand')) {
|
|
229
|
+
const selectedBrands = urlParams.get('brand').split(',');
|
|
230
|
+
selectedBrands.forEach(brand => {
|
|
231
|
+
const checkbox = document.querySelector(`input[name="brand"][value="${brand}"]`);
|
|
232
|
+
if (checkbox) checkbox.checked = true;
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Sort initialization - Reconstruct dropdown value from sort and order parameters
|
|
237
|
+
const sortToggleLabel = document.getElementById('sort-toggle-label');
|
|
238
|
+
const resultsSort = document.getElementById('results-sort');
|
|
239
|
+
const sortMenu = document.getElementById('sort-menu');
|
|
240
|
+
|
|
241
|
+
if (urlParams.has('sort')) {
|
|
242
|
+
const sortParam = urlParams.get('sort');
|
|
243
|
+
const orderParam = urlParams.get('order') || 'desc'; // Default to 'desc' if not specified
|
|
244
|
+
let dropdownValue = 'featured';
|
|
245
|
+
let sortLabel = 'Featured';
|
|
246
|
+
|
|
247
|
+
// Reconstruct the dropdown value format based on URL parameters
|
|
248
|
+
if (sortParam === 'created' && orderParam === 'desc') {
|
|
249
|
+
dropdownValue = 'newest';
|
|
250
|
+
sortLabel = 'Date, new to old';
|
|
251
|
+
} else if (sortParam === 'price' && orderParam === 'asc') {
|
|
252
|
+
dropdownValue = 'price-asc';
|
|
253
|
+
sortLabel = 'Price, low to high';
|
|
254
|
+
} else if (sortParam === 'price' && orderParam === 'desc') {
|
|
255
|
+
dropdownValue = 'price-desc';
|
|
256
|
+
sortLabel = 'Price, high to low';
|
|
257
|
+
} else if (sortParam === 'featured') {
|
|
258
|
+
// featured doesn't depend on order parameter
|
|
259
|
+
dropdownValue = 'featured';
|
|
260
|
+
sortLabel = 'Featured';
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Update the sort toggle label
|
|
264
|
+
if (sortToggleLabel) {
|
|
265
|
+
sortToggleLabel.textContent = `Sort: ${sortLabel}`;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Update the results sort label
|
|
269
|
+
if (resultsSort) {
|
|
270
|
+
resultsSort.textContent = `Sorted by ${sortLabel}`;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Mark the selected option as active
|
|
274
|
+
if (sortMenu) {
|
|
275
|
+
const sortOptions = sortMenu.querySelectorAll('.sort-option');
|
|
276
|
+
sortOptions.forEach(option => {
|
|
277
|
+
option.classList.remove('active', 'selected');
|
|
278
|
+
if (option.getAttribute('data-value') === dropdownValue) {
|
|
279
|
+
option.classList.add('active', 'selected');
|
|
280
|
+
option.setAttribute('aria-selected', 'true');
|
|
281
|
+
} else {
|
|
282
|
+
option.setAttribute('aria-selected', 'false');
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
} else {
|
|
287
|
+
// No sort parameter, show default
|
|
288
|
+
if (sortToggleLabel) {
|
|
289
|
+
sortToggleLabel.textContent = 'Sort: Featured';
|
|
290
|
+
}
|
|
291
|
+
if (resultsSort) {
|
|
292
|
+
resultsSort.textContent = 'Sorted by Featured';
|
|
293
|
+
}
|
|
294
|
+
// Mark featured as selected
|
|
295
|
+
if (sortMenu) {
|
|
296
|
+
const featuredOption = sortMenu.querySelector('.sort-option[data-value="featured"]');
|
|
297
|
+
if (featuredOption) {
|
|
298
|
+
featuredOption.classList.add('active', 'selected');
|
|
299
|
+
featuredOption.setAttribute('aria-selected', 'true');
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Show reset button if any filters are active
|
|
305
|
+
const resetFiltersBtn = document.getElementById('reset-filters');
|
|
306
|
+
if (resetFiltersBtn && (urlParams.has('price_min') || urlParams.has('price_max') ||
|
|
307
|
+
urlParams.has('in_stock') || urlParams.has('on_sale') || urlParams.has('brand'))) {
|
|
308
|
+
resetFiltersBtn.style.display = 'block';
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Initialize filters on page load
|
|
313
|
+
initializeFiltersFromURL();
|
|
314
|
+
|
|
315
|
+
// Filter functionality
|
|
316
|
+
const filterToggle = document.getElementById('filter-toggle');
|
|
317
|
+
const filterDropdown = document.getElementById('filter-dropdown');
|
|
318
|
+
const filterClose = document.getElementById('filter-close');
|
|
319
|
+
const clearFiltersBtn = document.getElementById('clear-filters');
|
|
320
|
+
const applyFiltersBtn = document.getElementById('apply-filters');
|
|
321
|
+
const resetFiltersBtn = document.getElementById('reset-filters');
|
|
322
|
+
const clearAllFiltersBtn = document.getElementById('clear-all-filters');
|
|
323
|
+
|
|
324
|
+
// Toggle filter dropdown
|
|
325
|
+
if (filterToggle && filterDropdown) {
|
|
326
|
+
filterToggle.addEventListener('click', function() {
|
|
327
|
+
filterDropdown.classList.toggle('active');
|
|
328
|
+
document.body.classList.toggle('filter-open');
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Close filter dropdown
|
|
333
|
+
if (filterClose) {
|
|
334
|
+
filterClose.addEventListener('click', function() {
|
|
335
|
+
filterDropdown.classList.remove('active');
|
|
336
|
+
document.body.classList.remove('filter-open');
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Close filter dropdown when clicking outside
|
|
341
|
+
document.addEventListener('click', function(e) {
|
|
342
|
+
if (filterDropdown && !filterDropdown.contains(e.target) && !filterToggle.contains(e.target)) {
|
|
343
|
+
filterDropdown.classList.remove('active');
|
|
344
|
+
document.body.classList.remove('filter-open');
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// Clear filters (but keep sort)
|
|
349
|
+
function clearAllFilters() {
|
|
350
|
+
// Navigate to page without filter parameters (but keep sort if present)
|
|
351
|
+
const params = new URLSearchParams(window.location.search);
|
|
352
|
+
params.delete('price_min');
|
|
353
|
+
params.delete('price_max');
|
|
354
|
+
params.delete('in_stock');
|
|
355
|
+
params.delete('on_sale');
|
|
356
|
+
params.delete('brand');
|
|
357
|
+
params.delete('page');
|
|
358
|
+
// Keep sort and order parameters
|
|
359
|
+
|
|
360
|
+
window.location.href = window.location.pathname + (params.toString() ? '?' + params.toString() : '');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (clearFiltersBtn) {
|
|
364
|
+
clearFiltersBtn.addEventListener('click', clearAllFilters);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (clearAllFiltersBtn) {
|
|
368
|
+
clearAllFiltersBtn.addEventListener('click', clearAllFilters);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (resetFiltersBtn) {
|
|
372
|
+
resetFiltersBtn.addEventListener('click', clearAllFilters);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Apply filters - Submit to server
|
|
376
|
+
if (applyFiltersBtn) {
|
|
377
|
+
applyFiltersBtn.addEventListener('click', function() {
|
|
378
|
+
applyFiltersToURL();
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Sorting functionality - custom dropdown
|
|
383
|
+
const sortDropdown = document.getElementById('sort-dropdown');
|
|
384
|
+
const sortToggle = document.getElementById('sort-toggle');
|
|
385
|
+
const sortMenu = document.getElementById('sort-menu');
|
|
386
|
+
const sortLabel = document.getElementById('sort-toggle-label');
|
|
387
|
+
|
|
388
|
+
function applySort(sortValue) {
|
|
389
|
+
const params = new URLSearchParams(window.location.search);
|
|
390
|
+
if (sortValue && sortValue !== 'featured') {
|
|
391
|
+
const parts = sortValue.split('-');
|
|
392
|
+
if (parts.length === 2) {
|
|
393
|
+
params.set('sort', parts[0]);
|
|
394
|
+
params.set('order', parts[1]);
|
|
395
|
+
} else if (sortValue === 'newest') {
|
|
396
|
+
params.set('sort', 'created');
|
|
397
|
+
params.set('order', 'desc');
|
|
398
|
+
}
|
|
399
|
+
} else {
|
|
400
|
+
params.set('sort', 'featured');
|
|
401
|
+
params.set('order', 'desc');
|
|
402
|
+
}
|
|
403
|
+
params.set('page', '1');
|
|
404
|
+
window.location.href = `${window.location.pathname}?${params.toString()}`;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (sortToggle && sortMenu) {
|
|
408
|
+
sortToggle.addEventListener('click', function() {
|
|
409
|
+
sortDropdown.classList.toggle('open');
|
|
410
|
+
const expanded = sortToggle.getAttribute('aria-expanded') === 'true';
|
|
411
|
+
sortToggle.setAttribute('aria-expanded', (!expanded).toString());
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
sortMenu.querySelectorAll('.sort-option').forEach(option => {
|
|
415
|
+
option.addEventListener('click', function() {
|
|
416
|
+
const value = this.getAttribute('data-value');
|
|
417
|
+
const label = this.getAttribute('data-label') || this.textContent;
|
|
418
|
+
|
|
419
|
+
// Update sort toggle label
|
|
420
|
+
if (sortLabel) {
|
|
421
|
+
sortLabel.textContent = `Sort: ${label}`;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Update results sort text
|
|
425
|
+
const resultsSort = document.getElementById('results-sort');
|
|
426
|
+
if (resultsSort) {
|
|
427
|
+
resultsSort.textContent = `Sorted by ${label}`;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Update active state
|
|
431
|
+
sortMenu.querySelectorAll('.sort-option').forEach(opt => {
|
|
432
|
+
opt.classList.remove('active', 'selected');
|
|
433
|
+
opt.setAttribute('aria-selected', 'false');
|
|
434
|
+
});
|
|
435
|
+
this.classList.add('active', 'selected');
|
|
436
|
+
this.setAttribute('aria-selected', 'true');
|
|
437
|
+
|
|
438
|
+
sortDropdown.classList.remove('open');
|
|
439
|
+
applySort(value);
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
// Close on outside click
|
|
444
|
+
document.addEventListener('click', function(e) {
|
|
445
|
+
if (!sortDropdown.contains(e.target)) {
|
|
446
|
+
sortDropdown.classList.remove('open');
|
|
447
|
+
sortToggle.setAttribute('aria-expanded', 'false');
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// View toggle functionality (client-side only for UI)
|
|
453
|
+
const viewBtns = document.querySelectorAll('.view-btn');
|
|
454
|
+
const productsGrid = document.getElementById('products-grid');
|
|
455
|
+
|
|
456
|
+
viewBtns.forEach(btn => {
|
|
457
|
+
btn.addEventListener('click', function() {
|
|
458
|
+
const view = this.dataset.view;
|
|
459
|
+
|
|
460
|
+
// Update active button
|
|
461
|
+
viewBtns.forEach(b => b.classList.remove('active'));
|
|
462
|
+
this.classList.add('active');
|
|
463
|
+
|
|
464
|
+
// Update grid class
|
|
465
|
+
if (productsGrid) {
|
|
466
|
+
productsGrid.dataset.view = view;
|
|
467
|
+
productsGrid.className = `products-grid products-${view}`;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Save preference to localStorage
|
|
471
|
+
localStorage.setItem('product-view-preference', view);
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
// Restore view preference
|
|
476
|
+
const savedView = localStorage.getItem('product-view-preference');
|
|
477
|
+
if (savedView && productsGrid) {
|
|
478
|
+
const viewBtn = document.querySelector(`.view-btn[data-view="${savedView}"]`);
|
|
479
|
+
if (viewBtn) {
|
|
480
|
+
viewBtns.forEach(b => b.classList.remove('active'));
|
|
481
|
+
viewBtn.classList.add('active');
|
|
482
|
+
productsGrid.dataset.view = savedView;
|
|
483
|
+
productsGrid.className = `products-grid products-${savedView}`;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Price range slider sync
|
|
488
|
+
const priceMin = document.getElementById('price-min');
|
|
489
|
+
const priceMax = document.getElementById('price-max');
|
|
490
|
+
const priceRangeMin = document.getElementById('price-range-min');
|
|
491
|
+
const priceRangeMax = document.getElementById('price-range-max');
|
|
492
|
+
|
|
493
|
+
if (priceMin && priceRangeMin) {
|
|
494
|
+
priceMin.addEventListener('input', function() {
|
|
495
|
+
priceRangeMin.value = this.value;
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (priceMax && priceRangeMax) {
|
|
500
|
+
priceMax.addEventListener('input', function() {
|
|
501
|
+
priceRangeMax.value = this.value;
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (priceRangeMin && priceMin) {
|
|
506
|
+
priceRangeMin.addEventListener('input', function() {
|
|
507
|
+
priceMin.value = this.value;
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (priceRangeMax && priceMax) {
|
|
512
|
+
priceRangeMax.addEventListener('input', function() {
|
|
513
|
+
priceMax.value = this.value;
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
</script>
|
|
518
|
+
|