first commit
This commit is contained in:
27
views/css/front.css
Normal file
27
views/css/front.css
Normal 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
117
views/js/front.js
Normal 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);
|
||||
});
|
||||
}
|
||||
12
views/templates/admin/support_panel.tpl
Normal file
12
views/templates/admin/support_panel.tpl
Normal 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>
|
||||
24
views/templates/hook/countdown.tpl
Normal file
24
views/templates/hook/countdown.tpl
Normal 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>
|
||||
Reference in New Issue
Block a user