--- phase: 02-product-actions-fixes plan: 03 status: complete date: 2026-04-23 --- # Plan 02-03 — Customization save + success modal (SUMMARY) ## Goal End-to-end add-to-cart UX w nowym layoucie: customization zapisuje się w DB (widoczne w koszyku jako "Szczegóły") + success modal po dodaniu. Parzystość ze starym layoutem. ## Result **Complete (5/5 AC pass).** Blocker na usunięcie IP gate — removed. ## AC Status (live Playwright verified 2026-04-23) | AC | Status | Evidence | |---|---|---| | AC-1 Customization save | ✓ PASS | Cart items "Rain of flowers" pokazują "Szczegóły produktu" z `Wymiar: Szerokość 2.0 m, Wysokość 1.5 m, 3.00 m2`. POST payload zawiera `discretion=on, calculated_total=3.0000, product_total_price_calc=717, qty_alt=2.0, qty_alth=1.5, dim=`. | | AC-2 Success modal | ✓ PASS | Modal `#blockcart-modal.modal` rendered po add-to-cart, z "Produkt pomyślnie dodany do koszyka", nazwą produktu, ceną, "Kontynuuj zakupy" + "Przejdź do kasy" buttons. | | AC-3 Cart "Szczegóły" | ✓ PASS | `/pl/koszyk` pokazuje "Szczegóły produktu" button, rozwija się na dimension data. | | AC-4 Single add | ✓ PASS | Po re-entrancy guard (`window.__p02p02InFlight`): 1× POST /pl/koszyk + 1× POST /module/ps_shoppingcart/ajax. Wcześniej było 2×. | | AC-5 OLD layout regression | ✓ PASS | Flip IP temporary → `.pp_stick_parent` present, `window.__p02p02Bound: false` (nasz handler NIE aktywowany w OLD). Structural isolation confirmed. | ## Key Discoveries (Task 1 — OLD payload capture via Playwright) **Source of truth:** fetched live POST payload z OLD layout (IP flipped to `255.255.255.255` temporarily, restored after). **26 fields w OLD `#add-to-cart-or-refresh` → `/pl/koszyk`:** ``` token, id_product, id_customization=0 dim="Szerokość {w}.0 m, Wysokość {h} m, {area}.00 m2, Pozycja {x} {y} ,Pozycja tła {bt} {bl} ,Odbicie {r} " converted_ea={area:.4f} calculated_total={area:.4f} grand_calculated_total="" extrafeevalue=0 wastevalue=0 fixed_price, base_price (przykład: 239) is_crop, is_reflection, crop_pos_x, crop_pos_y, crop_width, crop_height product_total_price_calc= piece_bg_top, piece_bg_left group[5]=9 (material) group[4]=5/6/7 (wariant kolorystyczny) qty_alt={width_m} qty_alth={height_m} discretion=on qty=1 (cart quantity) ``` **Kluczowe insighty:** - `dim` field jest VERBOSE Polish description, NIE "200x150" jak sugerował pierwotny plan. - `qty` (cart quantity) ≠ `qty_alt`/`qty_alth` (dimension values). Squaremeter uses alt fields. - `product_total_price_calc` = integer = `base_price × calculated_total` (area m²). - Wszystkie numeric fields stored as strings, decimals z 4 miejscami po przecinku dla converted_ea/calculated_total. **POST #2 (modal fetch):** - Endpoint: `/module/ps_shoppingcart/ajax` (POST) - Body: `action=add-to-cart&id_product&id_product_attribute&id_customization` - Response: `{preview: '', modal: '<#blockcart-modal Bootstrap HTML>'}` - Modal uses Bootstrap `#blockcart-modal.modal.fade`. Buttons: "Kontynuuj zakupy" (`data-dismiss`) + "Przejdź do kasy" (link → `/pl/koszyk?action=show`). ## Decisions | Data | Decyzja | Wpływ | |---|---|---| | 2026-04-23 | Używać verbose PL dim format (nie "200x150") | Kompatybilność z squaremeter customization hook + cart display consistency. | | 2026-04-23 | basePrice z `meta[property="product:price:amount"]` (fallback chain) | `#product_base_price`/`#product_fixed_price` nie istnieją w NEW layout (są tylko w OLD shared partial). Meta tag reliably renders w NEW. | | 2026-04-23 | `window.__p02p02InFlight` re-entrancy guard | Jedynie `__p02p02Bound` nie wystarczył — handler firował 2× (root cause niepotwierdzony, ale guard fix'uje skutecznie). Reset przez `complete:` callback. | | 2026-04-23 | Move inline script INSIDE `{block name='content'}` | **Kluczowy bug fix:** template użyje `{extends file=$layout}`. Smarty renderuje TYLKO content w blockach. Stary inline script (Plan 02-02) był MIĘDZY `{/block}` a `{/if}` — NIGDY nie renderowany. Znaczy: Plan 02-02 AC-3 pass było przez custom.js alone; inline mirror był dead code. | | 2026-04-23 | Manual FTP upload via curl | ftp-kr VSCode extension nie łapie edycji z Claude Code Edit tool. `curl -T ftp://` jako work-around. | | 2026-04-23 | Dual-source sq fields: DOM hidden inputs + explicit appended sqFields | Defense-in-depth. `$form.serialize()` jeśli shared partial ma pola. Appended wins w PHP $_POST (last-wins) gdyby brakowało. | ## Modified Files - `themes/ayon/assets/js/custom.js` (lines 1026-1200, ~175 new lines) — sq fields injection, modal chain, re-entrancy guard. - `themes/ayon/templates/catalog/product.tpl` (lines 756, 768-928) — moved `{/block}` to wrap inline script; added sq fields + modal chain in inline mirror. ## Boundaries respected - ✓ `modules/squaremeter/*` not modified - ✓ Shared partials (`themes/ayon/templates/catalog/_partials/*`) not modified - ✓ Old layout branch `{if != '89.69.31.86'}` unchanged - ✓ Plan 02-01 override of `totalpriceinfospecific` / `prod` preserved (still no-op) - ✓ No new dependencies ## Deferred (Plan 02-04+) - **Cena per-sqm UI** (live calculation on dimension change) — scope Plan 02-04. - **Systemowy cache-buster** `?v=` dla `