10 Commits

Author SHA1 Message Date
1db08c5f17 Add is_shoppingcart.tpl file 2026-04-24 09:52:03 +02:00
c911fc76d2 fix(falcon-PS9): Corrected selector for discount code
Discounts couldn't be added to the promotion code field on click since this selector was wrong.
2026-03-25 14:00:17 +01:00
dc8ed97eb0 fix: add shipping cost info to cart summary 2026-03-10 16:51:32 +01:00
a128906dc0 fix(falcon-PS9): prevent 500 error on AJAX filtering by normalizing data
Resolved a Fatal Error occurring during AJAX product listing requests (e.g., price slider).

- Issue: In PHP 8.2+, accessing properties of a 'CategoryLazyArray' object
  using array syntax (dot notation) triggers a Fatal Error.
- Cause: The Faceted Search module returns a LazyArray object during
  XHR requests, whereas standard page loads provide a native array.
- Solution: Implemented a normalization block using JSON serialization
  to flatten the object into a standard associative array. This ensures
  template syntax compatibility regardless of the request type or
  property visibility (protected vs public).
2026-01-09 10:52:21 +01:00
ddec414409 fix(falcon-PS9): Auto-format postcode
This removes the annoying error when you type in 8011XD instead of 8011 XD
2026-01-09 10:17:29 +01:00
48e776d80d feat(falcon-PS9): Add custom.scss file to follow 7-in-1 structure 2025-12-31 16:19:02 +01:00
abd2cdc145 fix(falcon-PS9): Removed nonexistent product-miniature-form import
Also added css folder locations
2025-11-20 14:35:36 +01:00
92a8db6e72 Merge branch 'feature/falcon-PS9' 2025-11-20 09:10:47 +01:00
44666297b3 fix: Improved active class added to account
Found a bug where when you click on "details"button in order history, all the customer links become active. This fixes that.
2025-11-19 17:29:32 +01:00
ae299283a7 Fix: Replace deprecated getBrightness with new method
New method created in is_themecore module: Uses use PrestaShop\PrestaShop\Core\Util\ColorBrightnessCalculator and adds as smarty function in SmartyHelperFunctions.php
2025-11-19 17:14:56 +01:00
14 changed files with 291 additions and 128 deletions

2
.gitignore vendored
View File

@@ -1,2 +1,4 @@
falcon/vendor/ falcon/vendor/
*.zip

View File

@@ -0,0 +1,19 @@
// NOTE: All bootstrap overrides have been configured under the abstracts/variables/bootstrap folder.
//Abstracts: Things used throughout the site such as utility classes and generic overrides.
//@import "custom/abstracts/base";
//@import "custom/abstracts/utilities";
// Components: parts of the theme itself that are not associated with a module.
//@import "custom/components/";
// Modules: Styling for specific modules.
//@import "custom/modules/";
//Layouts: Parts of a page, such as the header, footer, etc.
//@import "custom/layout/header";
//@import "custom/layout/footer";
// Pages
//@import "custom/pages/category";
//@import "custom/pages/product";

View File

@@ -1,2 +1,3 @@
@import "abstracts/index"; @import "abstracts/index";
@import "theme/index"; @import "theme/index";
@import "custom";

View File

@@ -5,3 +5,4 @@
@import "layout/index"; @import "layout/index";
@import "components/index"; @import "components/index";
@import "custom/custom";

View File

@@ -0,0 +1,22 @@
/* This is the main custom SCSS file for the Falcon theme.
I am loosely following the 7 in 1 structure for better organization of the styles. Learn more here:
https://medium.com/@diyorbekjuraev77/be-a-master-at-creating-the-7-1-sass-pattern-776fdfb5a3b1
NOTE: All bootstrap overrides have been configured under themes/falcon/_dev/css/abstracts/variables/bootstrap */
//Abstracts: Things used throughout the site such as utility classes and generic overrides.
//@import "abstracts/mixins";
// Components: parts of the theme itself that are not associated with a module.
//@import "components/buttons";
// Modules: Styling for specific modules.
//@import "modules/";
//Layouts: Parts of a page, such as the header, footer, etc.
//@import "layout/footer";
//@import "layout/header";
// Pages
//@import "pages/home";

View File

@@ -1,82 +1,84 @@
import prestashop from 'prestashop'; import prestashop from "prestashop";
import $ from 'jquery'; import $ from "jquery";
prestashop.themeSelectors = { prestashop.themeSelectors = {
product: { product: {
tabs: '.tabs .nav-link', tabs: ".tabs .nav-link",
activeNavClass: 'js-product-nav-active', activeNavClass: "js-product-nav-active",
activeTabClass: 'js-product-tab-active', activeTabClass: "js-product-tab-active",
activeTabs: '.tabs .nav-link.active, .js-product-nav-active', activeTabs: ".tabs .nav-link.active, .js-product-nav-active",
imagesModal: '.js-product-images-modal', imagesModal: ".js-product-images-modal",
thumb: '.js-thumb', thumb: ".js-thumb",
thumbContainer: '.thumb-container, .js-thumb-container', thumbContainer: ".thumb-container, .js-thumb-container",
arrows: '.js-arrows', arrows: ".js-arrows",
selected: '.selected, .js-thumb-selected', selected: ".selected, .js-thumb-selected",
modalProductCover: '.js-modal-product-cover', modalProductCover: ".js-modal-product-cover",
cover: '.js-qv-product-cover', cover: ".js-qv-product-cover",
customizationModal: '.js-customization-modal', customizationModal: ".js-customization-modal",
}, },
listing: { listing: {
searchFilterToggler: '#search_filter_toggler, .js-search-toggler', searchFilterToggler: "#search_filter_toggler, .js-search-toggler",
searchFiltersWrapper: '#search_filters_wrapper', searchFiltersWrapper: "#search_filters_wrapper",
searchFilterControls: '#search_filter_controls', searchFilterControls: "#search_filter_controls",
searchFilters: '#search_filters', searchFilters: "#search_filters",
activeSearchFilters: '#js-active-search-filters', activeSearchFilters: "#js-active-search-filters",
listTop: '#js-product-list-top', listTop: "#js-product-list-top",
list: '#js-product-list', list: "#js-product-list",
listBottom: '#js-product-list-bottom', listBottom: "#js-product-list-bottom",
listHeader: '#js-product-list-header', listHeader: "#js-product-list-header",
searchFiltersClearAll: '.js-search-filters-clear-all', searchFiltersClearAll: ".js-search-filters-clear-all",
searchLink: '.js-search-link', searchLink: ".js-search-link",
}, },
order: { order: {
returnForm: '#order-return-form, .js-order-return-form', returnForm: "#order-return-form, .js-order-return-form",
}, },
arrowDown: '.arrow-down, .js-arrow-down', arrowDown: ".arrow-down, .js-arrow-down",
arrowUp: '.arrow-up, .js-arrow-up', arrowUp: ".arrow-up, .js-arrow-up",
clear: '.clear', clear: ".clear",
fileInput: '.js-file-input', fileInput: ".js-file-input",
contentWrapper: '#content-wrapper, .js-content-wrapper', contentWrapper: "#content-wrapper, .js-content-wrapper",
footer: '#footer, .js-footer', footer: "#footer, .js-footer",
modalContent: '.js-modal-content', modalContent: ".js-modal-content",
modal: '.js-checkout-modal', modal: ".js-checkout-modal",
touchspin: '.js-touchspin', touchspin: ".js-touchspin",
checkout: { checkout: {
termsLink: '.js-terms a', termsLink: ".js-terms a",
giftCheckbox: '.js-gift-checkbox', giftCheckbox: ".js-gift-checkbox",
imagesLink: '.card-block .cart-summary-products p a, .js-show-details', imagesLink: ".card-block .cart-summary-products p a, .js-show-details",
carrierExtraContent: '.carrier-extra-content, .js-carrier-extra-content', carrierExtraContent:
btn: '.checkout a', ".carrier-extra-content, .js-carrier-extra-content",
btn: ".checkout a",
}, },
cart: { cart: {
productLineQty: '.js-cart-line-product-quantity', productLineQty: ".js-cart-line-product-quantity",
quickview: '.quickview', quickview: ".quickview",
touchspin: '.bootstrap-touchspin', touchspin: ".bootstrap-touchspin",
promoCode: '#promo-code', promoCode: "#promo-code",
displayPromo: '.display-promo', displayPromo: ".display-promo",
promoCodeButton: '.promo-code-button', promoCodeButton: ".promo-code-button",
discountCode: '.js-discount .code', discountCode: ".js-discount .js-code",
discountName: '[name=discount_name]', discountName: "[name=discount_name]",
actions: '[data-link-action="delete-from-cart"], [data-link-action="remove-voucher"]', actions:
'[data-link-action="delete-from-cart"], [data-link-action="remove-voucher"]',
}, },
notifications: { notifications: {
dangerAlert: '#notifications article.alert-danger', dangerAlert: "#notifications article.alert-danger",
container: '#notifications .container', container: "#notifications .container",
}, },
passwordPolicy: { passwordPolicy: {
template: '#password-feedback', template: "#password-feedback",
hint: '.js-hint-password', hint: ".js-hint-password",
container: '.js-password-strength-feedback', container: ".js-password-strength-feedback",
strengthText: '.js-password-strength-text', strengthText: ".js-password-strength-text",
requirementScore: '.js-password-requirements-score', requirementScore: ".js-password-requirements-score",
requirementLength: '.js-password-requirements-length', requirementLength: ".js-password-requirements-length",
requirementScoreIcon: '.js-password-requirements-score i', requirementScoreIcon: ".js-password-requirements-score i",
requirementLengthIcon: '.js-password-requirements-length i', requirementLengthIcon: ".js-password-requirements-length i",
progressBar: '.js-password-policy-progress-bar', progressBar: ".js-password-policy-progress-bar",
inputColumn: '.js-input-column', inputColumn: ".js-input-column",
}, },
}; };
$(() => { $(() => {
prestashop.emit('themeSelectorsInit'); prestashop.emit("themeSelectorsInit");
}); });

View File

@@ -1,28 +1,28 @@
import $ from 'jquery'; import $ from "jquery";
import '@js/theme/vendors/bootstrap/bootstrap-imports'; import "@js/theme/vendors/bootstrap/bootstrap-imports";
import 'bootstrap-touchspin'; import "bootstrap-touchspin";
import 'jquery-hoverintent'; import "jquery-hoverintent";
import '@js/theme/components/dynamic-bootstrap-components'; import "@js/theme/components/dynamic-bootstrap-components";
import bsCustomFileInput from 'bs-custom-file-input'; import bsCustomFileInput from "bs-custom-file-input";
import '@js/theme/components/selectors'; import "@js/theme/components/selectors";
import '@js/theme/components/sliders'; import "@js/theme/components/sliders";
import '@js/theme/components/responsive'; import "@js/theme/components/responsive";
import '@js/theme/components/customer'; import "@js/theme/components/customer";
import '@js/theme/components/quickview'; import "@js/theme/components/quickview";
import '@js/theme/components/product'; import "@js/theme/components/product";
import '@js/theme/components/cart/cart'; import "@js/theme/components/cart/cart";
import '@js/theme/components/cart/block-cart'; import "@js/theme/components/cart/block-cart";
import usePasswordPolicy from '@js/theme/components/usePasswordPolicy'; import usePasswordPolicy from "@js/theme/components/usePasswordPolicy";
import prestashop from 'prestashop'; import prestashop from "prestashop";
import EventEmitter from 'events'; import EventEmitter from "events";
import Form from '@js/theme/components/form'; import Form from "@js/theme/components/form";
import TopMenu from '@js/theme/components/TopMenu'; import TopMenu from "@js/theme/components/TopMenu";
import PageLazyLoad from '@js/theme/components/Lazyload'; import PageLazyLoad from "@js/theme/components/Lazyload";
import PageLoader from '@js/theme/components/PageLoader'; import PageLoader from "@js/theme/components/PageLoader";
import useStickyElement from '@js/theme/components/useStickyElement'; import useStickyElement from "@js/theme/components/useStickyElement";
/* eslint-disable */ /* eslint-disable */
// "inherit" EventEmitter // "inherit" EventEmitter
@@ -32,25 +32,34 @@ for (const i in EventEmitter.prototype) {
/* eslint-enable */ /* eslint-enable */
prestashop.pageLazyLoad = new PageLazyLoad({ prestashop.pageLazyLoad = new PageLazyLoad({
selector: '.lazyload', selector: ".lazyload",
}); });
prestashop.pageLoader = new PageLoader(); prestashop.pageLoader = new PageLoader();
function accLinksTriggerActive() { function accLinksTriggerActive() {
const url = window.location.pathname; const currentUrl = window.location.pathname + window.location.search;
$('.js-customer-links a').each((i, el) => {
const $el = $(el);
if ($el.attr('href').indexOf(url) !== -1) { $(".js-customer-links a").each((i, el) => {
$el.addClass('active'); const $el = $(el);
const linkHref = $el.attr("href");
let linkPath = linkHref;
try {
const linkUrl = new URL(linkHref, window.location.origin);
linkPath = linkUrl.pathname + linkUrl.search;
} catch (e) {}
if (
currentUrl === linkPath ||
(linkPath !== "/" && currentUrl.startsWith(linkPath))
) {
$el.addClass("active");
} }
}); });
} }
function initStickyHeader() { function initStickyHeader() {
const header = document.querySelector('.js-header-top'); const header = document.querySelector(".js-header-top");
const headerWrapper = document.querySelector('.js-header-top-wrapper'); const headerWrapper = document.querySelector(".js-header-top-wrapper");
if (header && headerWrapper) { if (header && headerWrapper) {
useStickyElement(header, headerWrapper); useStickyElement(header, headerWrapper);
@@ -62,12 +71,27 @@ $(() => {
accLinksTriggerActive(); accLinksTriggerActive();
Form.init(); Form.init();
bsCustomFileInput.init(); bsCustomFileInput.init();
const topMenu = new TopMenu('#_desktop_top_menu .js-main-menu'); const topMenu = new TopMenu("#_desktop_top_menu .js-main-menu");
usePasswordPolicy('.field-password-policy'); usePasswordPolicy(".field-password-policy");
topMenu.init(); topMenu.init();
$('.js-select-link').on('change', ({ target }) => { $(".js-select-link").on("change", ({ target }) => {
window.location.href = $(target).val(); window.location.href = $(target).val();
}); });
// Postcode input formatting
const $postCodeInput = $("input[name='postcode']");
$postCodeInput.on("input", function () {
let value = $(this).val();
// Match 4 digits followed by 2 letters (e.g., 1234AB)
const match = value.match(/^(\d{4})([a-zA-Z]{2})$/);
if (match) {
// Add space between numbers and letters
const formatted = `${match[1]} ${match[2]}`;
$(this).val(formatted);
}
});
}); });

View File

@@ -0,0 +1,79 @@
{**
* 2007-2020 PrestaShop and Contributors
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2020 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
* International Registered Trademark & Property of PrestaShop SA
*}
<div class="header-top__block header-top__block--cart col flex-grow-0">
<div class="js-blockcart blockcart cart-preview dropdown" data-refresh-url="{$refresh_url}">
<a href="#" role="button" id="cartDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
class="header-top__link d-lg-block d-none">
<div class="header-top__icon-container">
<span class="header-top__icon material-icons">shopping_basket</span>
<span class="header-top__badge {if $cart.products_count > 9}header-top__badge--smaller{/if}">
{$cart.products_count}
</span>
</div>
</a>
<a href="{$cart_url}" class="d-flex d-lg-none header-top__link">
<div class="header-top__icon-container">
<span class="header-top__icon material-icons">shopping_basket</span>
<span class="header-top__badge {if $cart.products_count > 9}header-top__badge--smaller{/if}">
{$cart.products_count}
</span>
</div>
</a>
<div class="dropdown-menu blockcart__dropdown cart-dropdown dropdown-menu-right" aria-labelledby="cartDropdown">
<div class="cart-dropdown__content keep-open js-cart__card-body cart__card-body">
<div class="cart-loader">
<div class="spinner-border text-primary" role="status"><span
class="sr-only">{l s='Loading...' d='Shop.Theme.Global'}</span></div>
</div>
<div class="cart-dropdown__title d-flex align-items-center mb-3">
<p class="h5 mb-0 mr-2">
{l s='Your cart' d='Modules.Isshoppingcart.Isshoppingcart'}
</p>
<a data-toggle="dropdown" href="#" class="cart-dropdown__close dropdown-close ml-auto cursor-pointer text-decoration-none">
<i class="material-icons d-block">close</i>
</a>
</div>
{if $cart.products_count > 0}
<div class="cart-dropdown__products pt-3 mb-3">
{foreach from=$cart.products item=product}
{include 'module:is_shoppingcart/views/templates/front/is_shoppingcart-product-line.tpl' product=$product}
{/foreach}
</div>
<div class="cart-summary-line cart-total">
<span class="label">{$cart.totals.total.label}</span>
<span class="value">{$cart.totals.total.value}</span>
</div>
<div class="mt-3">
<a href="{$cart_url}" class="btn btn-sm btn-primary btn-block dropdown-close">
{l s='Proceed to checkout' d='Shop.Theme.Actions'}
</a>
</div>
{else}
<div class="alert alert-warning">
{l s='Unfortunately your basket is empty' d='Modules.Isshoppingcart.Isshoppingcart'}
</div>
{/if}
</div>
</div>
</div>
</div>

View File

@@ -75,7 +75,7 @@
type="{if $facet.multipleSelectionAllowed}checkbox{else}radio{/if}" class="custom-control-input" type="{if $facet.multipleSelectionAllowed}checkbox{else}radio{/if}" class="custom-control-input"
{if $filter.active } checked{/if}> {if $filter.active } checked{/if}>
<label for="facet_input_{$_expand_id}_{$filter_key}" {if isset($filter.properties.color)} <label for="facet_input_{$_expand_id}_{$filter_key}" {if isset($filter.properties.color)}
class="custom-control-label custom-control-label-{if Tools::getBrightness($filter.properties.color) > 128}dark{else}bright{/if}" class="custom-control-label custom-control-label-{if $filter.properties.color|is_bright}dark{else}bright{/if}"
{else} class="custom-control-label" {else} class="custom-control-label"
{/if}> {/if}>
{if isset($filter.properties.color)} {if isset($filter.properties.color)}

View File

@@ -1,5 +1,13 @@
{* Defensive: Handles LazyArray objects for category *}
{if is_object($category)}
{$category = $category|json_encode|json_decode:true}
{if isset($category.category)}
{$category = $category.category}
{/if}
{/if}
<div id="js-product-list-footer"> <div id="js-product-list-footer">
{if $category.additional_description && $listing.pagination.items_shown_from == 1} {if isset($category.additional_description) && $category.additional_description && $listing.pagination.items_shown_from == 1}
<div id="category-description-2" class="cms-content my-3"> <div id="category-description-2" class="cms-content my-3">
{$category.additional_description nofilter} {$category.additional_description nofilter}
</div> </div>

View File

@@ -45,7 +45,7 @@
<div class="custom-control custom-radio-color"> <div class="custom-control custom-radio-color">
<input class="custom-control-input" type="radio" data-product-attribute="{$id_attribute_group}" id="{$id_attribute_group}_{$id_attribute}" name="group[{$id_attribute_group}]" value="{$id_attribute}" title="{$group_attribute.name}"{if $group_attribute.selected} checked="checked"{/if}> <input class="custom-control-input" type="radio" data-product-attribute="{$id_attribute_group}" id="{$id_attribute_group}_{$id_attribute}" name="group[{$id_attribute_group}]" value="{$id_attribute}" title="{$group_attribute.name}"{if $group_attribute.selected} checked="checked"{/if}>
<label class="custom-control-label {if $group_attribute.html_color_code}custom-control-label-{if Tools::getBrightness($group_attribute.html_color_code) > 128}dark{else}bright{/if}{/if}" for="{$id_attribute_group}_{$id_attribute}" aria-label="{$group_attribute.name}"> <label class="custom-control-label {if $group_attribute.html_color_code}custom-control-label-{if $group_attribute.html_color_code|is_bright}dark{else}bright{/if}{/if}" for="{$id_attribute_group}_{$id_attribute}" aria-label="{$group_attribute.name}">
<span <span
{if $group_attribute.texture} {if $group_attribute.texture}
class="custom-control-input-color" style="background-image: url({$group_attribute.texture})" class="custom-control-input-color" style="background-image: url({$group_attribute.texture})"

View File

@@ -38,6 +38,11 @@
<span class="label">{$cart.totals.total.label}&nbsp;{if $configuration.display_taxes_label && $configuration.taxes_enabled}{$cart.labels.tax_short}{/if}</span> <span class="label">{$cart.totals.total.label}&nbsp;{if $configuration.display_taxes_label && $configuration.taxes_enabled}{$cart.labels.tax_short}{/if}</span>
<span class="value">{$cart.totals.total.value}</span> <span class="value">{$cart.totals.total.value}</span>
</div> </div>
{if !$cart.has_delivery_address}
<p class="no_delivery_address_yet_message">
{l s='Shipping costs will be calculated in the next step, before you checkout. Shipping costs depend on the country the order is shipped to. For the Netherlands, orders with a value of € 125.00 will be shipped free of charge. You can view the shippingcosts <a href="https://www.nijssenbulbs.com/content/16-verzendkosten"><u>here</u></a>' d='Shop.Checkout.Total'}
</p>
{/if}
{/if} {/if}
{/block} {/block}

View File

@@ -60,7 +60,7 @@
<td class="hidden-md-down align-middle">{$order.details.payment}</td> <td class="hidden-md-down align-middle">{$order.details.payment}</td>
<td class="align-middle"> <td class="align-middle">
<span <span
class="label label-pill badge {if Tools::getBrightness($order.history.current.color) < 128}text-white{/if}" class="label label-pill badge {if !$order.history.current.color|is_bright}text-white{/if}"
style="background-color:{$order.history.current.color}" style="background-color:{$order.history.current.color}"
> >
{$order.history.current.ostate_name} {$order.history.current.ostate_name}

View File

@@ -89,7 +89,7 @@
<td>{$state.history_date}</td> <td>{$state.history_date}</td>
<td> <td>
<span <span
class="label label-pill badge {if Tools::getBrightness($state.color) < 128}text-white{/if}" class="label label-pill badge {if !$state.color|is_bright}text-white{/if}"
style="background-color:{$state.color}"> style="background-color:{$state.color}">
{$state.ostate_name} {$state.ostate_name}
</span> </span>