feat(new-layout): add-to-cart handler + piece configurator (Phase 02 plans 01-02)
Plan 02-01 (piece/crop configurator, complete):
- #piece reuse z shared partial product-cover-thumbnails.tpl
- 8 hidden inputs (is_crop, crop_pos_x/y, crop_width/height, piece_bg_top/left, is_reflection) w formie #add-to-cart-or-refresh
- Defensive setup w custom.js: setTimeout(600) init, no-op override totalpriceinfospecific/prod, DOM stubs
- CSS scope pod body#product .product-size-data .product-size-data--new
Plan 02-02 (add-to-cart submission, PARTIAL):
- Capture-phase native addEventListener (useCapture=true) blokuje PS core crash
(button poza formą w nowym layoucie — closest('form') zwracało 0)
- Manualny AJAX POST: form.serialize() + qty + add=1&action=update do /pl/koszyk
- Fancybox-blocker port z custom.js:327 (nie odpalał się bo selector 0 matches)
- Manual sync is_crop/crop_width/height przed POST (obejście crash checkedHandler)
- prestashop.emit('updatedCart') + defensive blockcart refresh fetch
- Loading spinner + success flash CSS
- Inline handler mirror w product.tpl z idempotency guard (window.__p02p02Bound)
— cache-buster dla browser cachowanego custom.js
Deferred do Plan 02-03 (customization + modal blocker dla production):
- Customization nie zapisuje się (squaremeter hook gate'owany discretion=on + brak dimension fields)
- Success modal (wymaga POST do /module/ps_shoppingcart/ajax)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -48,7 +48,9 @@
|
||||
{/if}
|
||||
{/block}
|
||||
|
||||
{if $smarty.server.REMOTE_ADDR != '91.189.216.43'}
|
||||
|
||||
|
||||
{if $smarty.server.REMOTE_ADDR != '89.69.31.86'}
|
||||
{block name='content'}
|
||||
|
||||
<section id="main" itemscope itemtype="https://schema.org/Product">
|
||||
@@ -520,7 +522,8 @@
|
||||
<div class="piece-left-positon hidden">10</div>
|
||||
<div class="piece-top-positon hidden">10</div>
|
||||
{/block}
|
||||
{else}
|
||||
{/if}
|
||||
{if $smarty.server.REMOTE_ADDR == '89.69.31.86'}
|
||||
{block name='content'}
|
||||
<section id="main" itemscope itemtype="https://schema.org/Product">
|
||||
<meta itemprop="url" content="{$product.url}">
|
||||
@@ -550,6 +553,7 @@
|
||||
{block name='product_cover_thumbnails'}
|
||||
{include file='catalog/_partials/product-cover-thumbnails.tpl'}
|
||||
{/block}
|
||||
{* #piece jest wewnatrz product-cover-thumbnails.tpl (rendered inside .product-images li) — NIE duplikowac tutaj *}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -598,9 +602,24 @@
|
||||
{block name='product_variants'}
|
||||
{$product_variant_mode = 2}
|
||||
|
||||
<div class="product-box product-variants-data">
|
||||
<div class="product-box product-variants-data product-variants-data--new">
|
||||
<h4 class="block-title">Wybierz wersję kolorystyczną</h4>
|
||||
{include file='catalog/_partials/product-variants.tpl'}
|
||||
<form action="{$urls.pages.cart}" method="post" id="add-to-cart-or-refresh">
|
||||
<input type="hidden" name="token" value="{$static_token}">
|
||||
<input type="hidden" name="id_product" value="{$product.id}" id="product_page_product_id">
|
||||
<input type="hidden" name="id_customization" value="{$product.id_customization}" id="product_customization_id" class="js-product-customization-id">
|
||||
<input type="hidden" name="is_crop" value="0" id="product_is_crop">
|
||||
<input type="hidden" name="is_reflection" value="0" id="product_is_reflection">
|
||||
<input type="hidden" name="crop_pos_x" value="0" id="product_crop_pos_x">
|
||||
<input type="hidden" name="crop_pos_y" value="0" id="product_crop_pos_y">
|
||||
<input type="hidden" name="crop_width" value="0" id="product_crop_width">
|
||||
<input type="hidden" name="crop_height" value="0" id="product_crop_height">
|
||||
<input type="hidden" name="piece_bg_top" id="piece_bg_top" value="">
|
||||
<input type="hidden" name="piece_bg_left" id="piece_bg_left" value="">
|
||||
<div class="product-variants-grid">
|
||||
{include file='catalog/_partials/product-variants.tpl'}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{/block}
|
||||
|
||||
@@ -612,6 +631,26 @@
|
||||
</a>
|
||||
</div>
|
||||
<div class="product-box--data">
|
||||
<div class="product-size-data--new">
|
||||
<a rel="nofollow" href="javascript:void(0);" class="fancybox-size-controls piece-summary">
|
||||
<span id="piece-size-view" class="strong">Wybierz rozmiar</span>
|
||||
<span class="piece-hint">— kliknij aby zmienić</span>
|
||||
</a>
|
||||
<div id="button-mirror-reflection">
|
||||
<div class="product-bar-icon rotate-icon">
|
||||
<img src="/themes/ayon/assets/images/odbicie-iustrzane.png" alt="">
|
||||
</div>
|
||||
<div class="product-bar-box">
|
||||
<p class="button-mirror-reflection-label">Odbicie lustrzane</p>
|
||||
</div>
|
||||
</div>
|
||||
{* Hidden state trzymane w DOM — istniejące handlery w custom.js bindują się po ID. *}
|
||||
<input type="checkbox" id="checkbox-piece" checked style="display:none;">
|
||||
<input type="number" min="50" max="500" value="100" id="piece-width" style="display:none;">
|
||||
<input type="number" min="50" max="300" value="100" id="piece-height" style="display:none;">
|
||||
<div class="piece-left-positon" style="display:none;">10</div>
|
||||
<div class="piece-top-positon" style="display:none;">10</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/block}
|
||||
@@ -715,5 +754,107 @@
|
||||
</div>
|
||||
</section>
|
||||
{/block}
|
||||
|
||||
{* =========================================================================
|
||||
Phase 02 Plan 02-02: inline add-to-cart handler (cache-buster).
|
||||
Powod: <script src="custom.js"> w tym temacie jest serwowany bez wersji
|
||||
i browser cache'uje stara wersje przy kolejnych iteracjach. Ten inline
|
||||
<script> jest czescia HTML response wiec ZAWSZE jest swiezy. Idempotentny
|
||||
guard `window.__p02p02Bound` zapobiega double-register jesli custom.js
|
||||
tez jest aktualny (happy path z hard-reload).
|
||||
Kod IDENTYCZNY z custom.js:994-1113 — zmiany wprowadzaj w OBU miejscach
|
||||
do czasu dodania systemowego cache-bustera (Plan 02-03+).
|
||||
========================================================================= *}
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
if (window.__p02p02Bound) return;
|
||||
window.__p02p02Bound = true;
|
||||
|
||||
document.addEventListener('click', function(e) {
|
||||
var btn = e.target.closest ? e.target.closest('[data-button-action=add-to-cart]') : null;
|
||||
if (!btn) return;
|
||||
if (!document.querySelector('.product-variants-data--new')) return;
|
||||
|
||||
var $form = jQuery('#add-to-cart-or-refresh');
|
||||
if (!$form.length) return;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
e.stopPropagation();
|
||||
|
||||
if (!jQuery('#checkbox-piece').is(':checked')) {
|
||||
jQuery.fancybox({
|
||||
minWidth: 800, maxWidth: 1000, padding: 30, height: 100,
|
||||
content: 'Proszę wybrać rozmiar i wycinek tapety przed dodaniem jej do koszyka.'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (jQuery('#product_is_crop').val() === '0' || !jQuery('#product_crop_width').val() || jQuery('#product_crop_width').val() === '0') {
|
||||
jQuery('#product_is_crop').val('1');
|
||||
var pw = parseInt(jQuery('#piece-width').val(), 10) || 100;
|
||||
var ph = parseInt(jQuery('#piece-height').val(), 10) || 100;
|
||||
jQuery('#product_crop_width').val(pw);
|
||||
jQuery('#product_crop_height').val(ph);
|
||||
}
|
||||
|
||||
var $btn = jQuery(btn);
|
||||
$btn.prop('disabled', true).addClass('loading');
|
||||
|
||||
var qty = parseInt(jQuery('#quantity_wanted').val(), 10) || 1;
|
||||
var payload = $form.serialize() + '&qty=' + encodeURIComponent(qty) + '&add=1&action=update';
|
||||
var actionUrl = $form.attr('action') || window.location.href;
|
||||
|
||||
jQuery.ajax({
|
||||
url: actionUrl,
|
||||
type: 'POST',
|
||||
data: payload,
|
||||
dataType: 'json',
|
||||
headers: { 'Accept': 'application/json' },
|
||||
success: function(resp) {
|
||||
var hasError = !resp || resp.hasError === true || resp.success === false ||
|
||||
(resp.errors && (Array.isArray(resp.errors) ? resp.errors.length : Object.keys(resp.errors).length));
|
||||
if (hasError) {
|
||||
var errs = resp && resp.errors;
|
||||
var msg = '';
|
||||
if (Array.isArray(errs)) msg = errs.join('<br>');
|
||||
else if (errs && typeof errs === 'object') msg = Object.values(errs).join('<br>');
|
||||
else msg = 'Nie udało się dodać produktu do koszyka. Spróbuj ponownie.';
|
||||
jQuery.fancybox({
|
||||
minWidth: 400, maxWidth: 800, padding: 30, height: 100,
|
||||
content: msg
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (window.prestashop && typeof prestashop.emit === 'function') {
|
||||
prestashop.emit('updatedCart', { resp: resp, reason: { linkAction: 'add-to-cart' } });
|
||||
}
|
||||
jQuery(document).trigger('updatedCart', [resp]);
|
||||
|
||||
if (window.prestashop && prestashop.urls && prestashop.urls.pages && prestashop.urls.pages.cart) {
|
||||
jQuery.get(prestashop.urls.pages.cart, { action: 'refresh', ajax: 1 }, null, 'json')
|
||||
.done(function(cartResp) {
|
||||
if (cartResp && cartResp.preview) {
|
||||
jQuery('.blockcart').replaceWith(cartResp.preview);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$btn.addClass('added-flash');
|
||||
setTimeout(function() { $btn.removeClass('added-flash'); }, 1200);
|
||||
},
|
||||
error: function() {
|
||||
jQuery.fancybox({
|
||||
minWidth: 400, maxWidth: 800, padding: 30, height: 100,
|
||||
content: 'Błąd połączenia z serwerem. Spróbuj ponownie za chwilę.'
|
||||
});
|
||||
},
|
||||
complete: function() {
|
||||
$btn.prop('disabled', false).removeClass('loading');
|
||||
}
|
||||
});
|
||||
}, true);
|
||||
})();
|
||||
</script>
|
||||
{/if}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user