` (ok. linia 719 w nowym layoucie)
i owinąć go w `
`:
```smarty
```
- UWAGA: sprawdzić czy klasa `.product-actions` nie wywołuje niechcianych stylów
ze starego SCSS (grep w custom.css). Jeśli tak — zmienić selector CSS na
scope'owany (`body#product .product-variants-data--new .product-actions`)
lub dodać marker class `.product-actions--new` i użyć jej w JS.
**Jeśli S3 (własny submit handler):**
- W custom.js, w obrębie istniejącego `setTimeout(..., 600)` init bloku
(dla nowego layoutu, gated przez `.product-variants-data--new` check),
dodać handler:
```js
$('#add-to-cart-or-refresh').on('submit', function(e) {
if ($('.product-variants-data--new').length === 0) return; // old layout: PS core handles
e.preventDefault();
if (!$('#checkbox-piece').is(':checked')) {
// fallback do istniejącej fancybox blokady — delegacja
$('#add-to-cart-or-refresh button').trigger('click');
return;
}
var $btn = $(this).find('[data-button-action=add-to-cart]');
$btn.prop('disabled', true).addClass('loading');
$.ajax({
url: this.action,
method: 'POST',
data: $(this).serialize() + '&add=1&action=update',
dataType: 'json',
headers: { 'Accept': 'application/json' },
success: function(resp) {
if (resp.hasError || resp.success === false) {
// show error (use existing fancybox or inline)
$.fancybox({ content: (resp.errors || ['Błąd dodawania do koszyka']).join('
') });
return;
}
if (window.prestashop && typeof prestashop.emit === 'function') {
prestashop.emit('updatedCart', { resp: resp, reason: { linkAction: 'add-to-cart' } });
}
},
error: function() {
$.fancybox({ content: 'Błąd połączenia. Spróbuj ponownie.' });
},
complete: function() {
$btn.prop('disabled', false).removeClass('loading');
}
});
});
```
- NIE dotykać istniejącego `$('#add-to-cart-or-refresh button').on('click')` z custom.js:327
(blokada "wybierz rozmiar przed add-to-cart") — nadal potrzebna dla AC-2.
- Uwaga: PS core używa `add=1&action=update` dla add-to-cart — sprawdzić empirycznie
(w Task 1 powinno być w network tab jeśli PS core działa gdziekolwiek).
Avoid:
- Modyfikowanie `product-add-to-cart.tpl` partial (współdzielony ze starym layoutem, risk regresji)
- Dodawanie globalnych handlerów `$(document).on(...)` bez scope'owania na new layout
- Nadpisywanie istniejącego click handler z custom.js:327 (blokada fancybox)
Playwright live test:
1. Load produkt w nowym layoucie (IP match)
2. Piece config flow (width=200, height=150, position drag)
3. Click "Dodaj do koszyka"
4. Odczytać `window.__addToCartDiag` + network tab:
- `posts.length >= 1` z body zawierającym `is_crop=1&crop_width=200&crop_height=150&...`
- Response HTTP 200, `success: true`
- Event `updatedCart` emitted
- Header cart counter += 1
AC-1, AC-2, AC-3 satisfied (AC-2 nietknięta przez Task 2 — istniejąca blokada nadal działa).
Task 3: Error handling, loading state UX, cross-layout regression check
themes/ayon/assets/js/custom.js, themes/ayon/assets/css/custom.scss
**Error UX (jeśli S3 w Task 2 — jeśli S1/S2 sprawdzić czy PS core natywnie pokazuje błędy):**
- W custom.js, w submit handler z Task 2, branch `hasError`:
- Wyciągnąć czytelny text z `resp.errors` (array) → `join('
')`
- Fallback: "Nie udało się dodać do koszyka. Spróbuj ponownie."
- Pokazać w `$.fancybox({ content: ... })` zgodnie z istniejącym wzorcem (custom.js:330)
**Loading state (wszystkie scenariusze):**
- W custom.scss, scope'owana reguła (~linia koniec pliku):
```scss
body#product .product-variants-data--new {
#add-to-cart-or-refresh button.add-to-cart {
&.loading {
opacity: 0.6;
pointer-events: none;
position: relative;
&::after {
content: '';
position: absolute;
top: 50%; left: 50%;
width: 20px; height: 20px;
margin: -10px 0 0 -10px;
border: 2px solid rgba(255,255,255,.3);
border-top-color: #fff;
border-radius: 50%;
animation: spin .6s linear infinite;
}
}
}
}
@keyframes spin { to { transform: rotate(360deg); } }
```
(Skip jeśli `@keyframes spin` już istnieje — grep sprawdzić.)
**Regression test — stary layout:**
- Playwright na URL z REMOTE_ADDR != 89.69.31.86 (np. przez VPN lub override header Host).
- Zweryfikować że flow add-to-cart w starym layoucie działa identycznie jak przed Plan 02-02:
- Klik → POST → cart update → success
- Network payload (pomijając cookies) identyczny z baseline'em (zarejestrować baseline przed Task 2 jako kontrolę)
- Jeśli regresja wykryta → zdiagnozować jaki selektor / listener konflict'uje → scope'ować ciaśniej.
Avoid:
- Dodawanie globalnych styles (wszystkie pod `.product-variants-data--new` scope)
- Zmiana istniejącego submit flow starego layoutu (nawet inline — stary działa, nie dotykamy)
1. Playwright nowy layout: force error (np. Console `fetch('/cart', {...})` z złym tokenem) → fancybox z błędem pokazuje się, button wraca do enabled.
2. Playwright nowy layout: slow-3G throttling → spinner widoczny podczas pending request.
3. Playwright stary layout (IP != 89.69.31.86): add-to-cart flow identyczny z baseline; headless log network + compare.
AC-4, AC-5 satisfied.
## DO NOT CHANGE
- `themes/ayon/templates/catalog/_partials/product-add-to-cart.tpl` (współdzielony ze starym layoutem — modyfikacja = ryzyko regresji w produkcji)
- `themes/ayon/templates/catalog/_partials/product-variants.tpl` (shared, Plan 01 closed)
- `themes/ayon/templates/catalog/_partials/product-cover-thumbnails.tpl` (shared, używany przez piece z Plan 02-01)
- Starą gałąź `{if ... != '89.69.31.86'}` w `product.tpl` — całość strukturalnie nietknięta
- Istniejący handler `$('#add-to-cart-or-refresh button').on('click', ...)` z custom.js:327 (blokada fancybox — nadal pełni AC-2)
- Hidden inputy crop/mirror z Plan 02-01 (nazwy/ID są kontraktem)
- `custom.css` — edytujemy wyłącznie `custom.scss` (user ma watcher)
## SCOPE LIMITS
- Ten plan NIE rozwiązuje kalkulacji ceny per-sqm w nowym layoucie (`totalpriceinfospecific` no-op override z Plan 02-01) — to osobny plan (Plan 02-03 kandydat).
- Ten plan NIE wypełnia pustych bloków (`.product-protect`, `.product-installation`, `.product-order-sample`) — osobny plan (Plan 02-04+).
- Ten plan NIE dodaje "quick view" / product modalu — zakres strony produktu tylko.
- Ten plan NIE zmienia serwer-side logic (add-to-cart controller) — tylko client + template.
- Brak nowych zależności npm / composer — wykorzystujemy istniejący stack (jQuery, fancybox, PS core).
Przed declaration complete (UNIFY gate):
- [ ] AC-1 verified Playwright: POST wychodzi z pełnym body, success response.
- [ ] AC-2 verified Playwright: blokada fancybox gdy piece nie wybrany.
- [ ] AC-3 verified Playwright: cart counter w headerze aktualizuje się.
- [ ] AC-4 verified Playwright: error response → czytelny komunikat, button enabled.
- [ ] AC-5 verified Playwright: stary layout bez regresji (baseline diff clean).
- [ ] `custom.scss` → `custom.css` compile clean (user watcher lub manual check po commit).
- [ ] Grep: zero zmian w plikach starego layoutu / shared partial'ach (git diff).
- [ ] `$('#add-to-cart-or-refresh').serialize()` w nowym layoucie zawiera wszystkie 8 hidden input'ów + token + id_product + id_customization + qty.
- Wszystkie 5 AC pass w live Playwright test.
- Zero regresji w starym layoucie (Playwright + git diff check).
- Zero nowych dependencies.
- `custom.js` zmiany dodatkowe (nie modyfikujące istniejących handlerów) — diff czysty, scope'owany.
- SUMMARY.md wystawiony z: co działa, co odroczone (cena = Plan 02-03), problemy napotkane.