first commit

This commit is contained in:
O K
2025-09-07 12:50:02 +03:00
commit ba8c1e6957
10 changed files with 843 additions and 0 deletions

27
views/css/front.css Normal file
View File

@@ -0,0 +1,27 @@
.product-countdown-container {
margin-top: 10px;
margin-bottom: 10px;
clear: both;
}
.product-countdown-container .countdown-name {
font-weight: bold;
margin-bottom: 5px;
font-size: 0.9rem;
color: #333;
}
/* MODIFIED: Changed selector from .badge to .countdown-badge */
.product-countdown-container .countdown-wrapper .countdown-badge {
padding: 0.5em 0.75em;
font-size: 1rem;
font-weight: 600;
/* Colors are now handled by inline styles, so they are not needed here */
}
.product-countdown-container .countdown-timer {
display: inline-block;
margin-left: 5px;
min-width: 120px;
text-align: left;
}

117
views/js/front.js Normal file
View File

@@ -0,0 +1,117 @@
/**
* Product Discount Countdown Module
*
* This script initializes countdown timers on the product page.
* It handles the initial page load and the dynamic updates that occur
* when a customer changes a product combination (attribute).
*/
// A global array to keep track of our active timer intervals.
// This is crucial for cleanup when the product block is updated via AJAX.
let productCountdownIntervals = [];
/**
* Scans the DOM for countdown containers and initializes them.
* This function is designed to be called on page load and after AJAX updates.
*/
function initializeProductCountdowns() {
// 1. Clear any previously running timers.
// This prevents multiple timers from running after a product combination change.
productCountdownIntervals.forEach(intervalId => clearInterval(intervalId));
productCountdownIntervals = [];
// 2. Find all countdown containers on the page that need to be processed.
const countdownContainers = document.querySelectorAll('.product-countdown-container');
countdownContainers.forEach(container => {
// Check if this specific container has already been initialized to avoid race conditions.
if (container.dataset.initialized === 'true') {
return;
}
const timerElement = container.querySelector('.countdown-timer');
if (!timerElement) return;
// Mark as initialized
container.dataset.initialized = 'true';
const targetTimestamp = parseInt(container.dataset.timestamp, 10);
const onExpireAction = container.dataset.onExpire;
const expiredText = container.dataset.expiredText;
// Get translated units from hidden spans
const units = {
day: container.querySelector('[data-unit="day"]').textContent,
days: container.querySelector('[data-unit="days"]').textContent,
hr: container.querySelector('[data-unit="hr"]').textContent,
min: container.querySelector('[data-unit="min"]').textContent,
sec: container.querySelector('[data-unit="sec"]').textContent
};
function updateTimer() {
const now = Math.floor(new Date().getTime() / 1000);
const diff = targetTimestamp - now;
if (diff <= 0) {
clearInterval(interval);
handleExpiry();
return;
}
const d = Math.floor(diff / (60 * 60 * 24));
const h = Math.floor((diff % (60 * 60 * 24)) / (60 * 60));
const m = Math.floor((diff % (60 * 60)) / 60);
const s = Math.floor(diff % 60);
const parts = [];
if (d > 0) {
parts.push(`${d} ${d > 1 ? units.days : units.day}`);
}
// Always show hours, minutes, and seconds for a better countdown experience
parts.push(`${String(h).padStart(2, '0')}${units.hr}`);
parts.push(`${String(m).padStart(2, '0')}${units.min}`);
parts.push(`${String(s).padStart(2, '0')}${units.sec}`);
timerElement.textContent = parts.join(' ');
}
function handleExpiry() {
switch (onExpireAction) {
case 'reload':
location.reload();
break;
case 'message':
container.querySelector('.countdown-wrapper').innerHTML = `<span class="badge badge-secondary">${expiredText}</span>`;
break;
case 'hide':
default:
container.style.display = 'none';
break;
}
}
const interval = setInterval(updateTimer, 1000);
// Add the new interval ID to our global array for tracking.
productCountdownIntervals.push(interval);
updateTimer(); // Initial call to display the timer immediately
});
}
// --- Event Listeners ---
// 1. Run the initializer on the initial page load.
document.addEventListener('DOMContentLoaded', () => {
initializeProductCountdowns();
});
// 2. Run the initializer whenever PrestaShop updates the product block via AJAX.
// The `prestashop` object is globally available in modern themes.
if (typeof prestashop !== 'undefined') {
prestashop.on('updateProduct', (data) => {
// We use a small timeout to ensure the DOM has been fully updated by PrestaShop's scripts
// before our script runs and looks for the new container. 100ms is usually safe.
setTimeout(() => {
initializeProductCountdowns();
}, 100);
});
}

View File

@@ -0,0 +1,12 @@
<div class="panel">
<div class="panel-heading">
<i class="icon-heart"></i> {$panel_title}
</div>
<div class="panel-body">
<p>{$panel_body_text_1}</p>
<p>{$panel_body_text_2}</p>
<a href="{$donation_link}" target="_blank" rel="noopener noreferrer" class="btn btn-success btn-lg btn-block">
<i class="icon-rocket"></i> {$button_text}
</a>
</div>
</div>

View File

@@ -0,0 +1,24 @@
<div class="product-countdown-container" id="product-countdown-{$product.id}" data-timestamp="{$countdown_timestamp}"
data-on-expire="{$countdown_on_expire_action}"
data-expired-text="{l s=$countdown_expired_text d='Modules.Productcountdown.Shop'}">
{if !empty($countdown_discount_name)}
<p class="countdown-name">{$countdown_discount_name|escape:'htmlall':'UTF-8'}</p>
{/if}
<div class="countdown-wrapper h5">
{* MODIFIED: Added style attribute and changed class *}
<span class="badge countdown-badge"
style="background-color: {$countdown_bg_color|escape:'html':'UTF-8'}; color: {$countdown_text_color|escape:'html':'UTF-8'}; border-color: {$countdown_bg_color|escape:'html':'UTF-8'};">
{l s=$countdown_prefix d='Modules.Productcountdown.Shop'}
<span class="countdown-timer"></span>
</span>
</div>
{* These hidden spans provide translated units for the javascript, no changes here *}
<span class="d-none" data-unit="day">{l s='day' d='Modules.Productcountdown.Shop'}</span>
<span class="d-none" data-unit="days">{l s='days' d='Modules.Productcountdown.Shop'}</span>
<span class="d-none" data-unit="hr">{l s='hr' d='Modules.Productcountdown.Shop'}</span>
<span class="d-none" data-unit="min">{l s='min' d='Modules.Productcountdown.Shop'}</span>
<span class="d-none" data-unit="sec">{l s='sec' d='Modules.Productcountdown.Shop'}</span>
</div>