36 Commits

Author SHA1 Message Date
92811693ab feat(extra-features): Shows user's name or "log out" if logged in 2026-03-10 17:45:46 +01:00
e3cb3ece45 feat(extra-features): Add border color settings 2026-03-10 17:21:39 +01:00
be58a92df3 feat(extra-features): introduce .section-spacer-bottom utility class 2026-03-10 17:01:25 +01:00
c522059d06 refactor(extra-features): enhance utility variables and section spacer logic 2026-03-10 17:01:24 +01:00
d5c3a60e37 fix: add shipping cost info to cart summary 2026-03-10 16:51:04 +01:00
f848833091 feat(extra-features): synchronize product thumbnail height 2026-03-10 16:51:04 +01:00
e209ddf5d9 feat(extra-features): improve outline button text color on hover 2026-03-10 16:51:03 +01:00
b208b00259 feat(extra-features): enable abstract utilities and cleanup imports 2026-03-10 16:51:03 +01:00
541901e8e6 feat(extra-features): A bunch of improvements 2026-02-06 14:20:14 +01:00
b968033b17 feat(extra-features): Standardize touchspin
bootstrap touchspin default styles
2026-01-15 16:37:59 +01:00
16b4475db9 feat(extra-features): Edit brand tpl file
Fix image url; edit cards
2026-01-14 12:57:23 +01:00
98ccf560c9 fix(extra-features): Product card grid now uses bootstrap cols
The product cards should use bootstrap instead of display grid for easier edits and consistency
2026-01-09 15:51:22 +01:00
e393572081 fet(extra-fetures): Pagination results text
Shows "12 van 15 resultaten" for example right acorss from the pagination
2026-01-09 15:41:10 +01:00
e37cafd839 fix(extra-features): Remove btn-outline in loop
The colors for btn-outline should be the same as the border
2026-01-09 14:22:34 +01:00
1bf0ea3450 Merge branch 'feature/falcon-PS9' into feature/extra-features 2026-01-09 10:53:45 +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
2f8a8bf527 feat(extra-features): Add section-spacer utility class
This is  a class used to standardize spacing between sections of the site, especially the home page.
2026-01-06 11:54:52 +01:00
e4c471684f feat(extra-features): Add more helpful functions 2026-01-06 11:40:34 +01:00
d38338b360 Merge remote-tracking branch 'origin/dewebsmid' into restore-old 2025-12-31 19:15:05 +01:00
cb65915c7c Merge remote-tracking branch 'origin/feature/svg_icon' into restore-old 2025-12-31 19:12:56 +01:00
10aabb24a9 Merge branch 'feature/scss' into dewebsmid 2025-12-31 18:24:50 +01:00
ff9b9a3570 feat(scss): Replace font-size-base to 14px
This has been standard in the projects I've worked on so far so might as well replace it.
2025-12-31 16:57:43 +01:00
47f985815a feat(scss): Add gap and font-size utility functions 2025-12-31 16:56:10 +01:00
8c79477559 feat(scss): Add bootstrap-5-like grids & media queries
This branch is just for "good to have" optional stand-alone sass features.

In this commit, I aimed to make some things more like bootstrap 5 for ease of use. I  changed the grid breakpoints to match that of Bootstrap 5's and added media query mixins that are more logical like in bootstrap 5.
2025-12-31 16:54:02 +01:00
ea6ebb4df1 feat(standard-styling):add svg_icon support for favorite_products 2025-12-31 14:03:31 +01:00
efa88a508a feat(standard-styling): Add button color map 2025-12-24 11:55:09 +01:00
27dfb4dc70 feat(standard-styling) Enhance search and styling
Improves search bar with SVG icon support for better visual consistency.

Adds utility SCSS file for managing global styles like heading selectors.

Introduces a checkmark SVG asset and reorganizes custom SCSS files following the 7-in-1 structure for better maintainability.

Comments out optional USP bar code in header.tpl to allow easy activation.
2025-12-24 09:37:59 +01:00
7ba1dfd4c8 fix(standard-styling): Remove duplicate displayNavFullWidth 2025-12-23 09:35:14 +01:00
80a44174be feat(standard-styling): Replace font-size-base to 14px
This is standard
2025-12-22 11:36:29 +01:00
f6df65fbb3 Adds standard styling for header, cart, and search
Applies new styling to the header, shopping cart, and search bar components.
Creates new scss files for header and swiper layouts.
Replaces x.svg.

These changes create a more consistent and modern user interface.
2025-12-18 12:02:00 +01:00
97b88c111c fix(standard-styling): Fix structure of custom css 2025-12-10 10:34:45 +01:00
879ed8b9ee feat(standard-styling): Fix icons; add standard css 2025-12-10 09:51:39 +01:00
9609cfe305 feat(standard-styling): Add standard svg icons
Use chevron svg icons in bootstrap touchspin on product page AND on main menu if it has children
2025-12-04 15:21:42 +01:00
00bae73b17 feat(standard-styling): Change header tpl, standard vars & icons, 2025-11-21 14:41:10 +01:00
79512bd81a FIX(standard-styling): Header 2025-11-20 14:54:59 +01:00
43 changed files with 936 additions and 518 deletions

View File

@@ -1,5 +1,12 @@
$border-width: 1px;
$border-color: $gray-300;
$border-radius: .4rem;
$border-radius-lg: .4rem;
$border-radius-sm: .4rem;
$border-width: 1px;
$border-color: $gray-300;
$border-radius: 0.4rem;
$border-radius-lg: 0.4rem;
$border-radius-sm: 0.4rem;
$card-border-color: $border-color;
$table-border-color: $border-color;
$input-border-color: $border-color;
$input-focus-border-color: $border-color;
$form-check-input-border: $border-color;

View File

@@ -1,8 +1,10 @@
$input-btn-padding-y: rem-calc(8px);
$input-btn-padding-x: rem-calc(16px);
$input-btn-padding-y: rem-calc(8px);
$input-btn-padding-x: rem-calc(16px);
$input-btn-padding-y-sm: rem-calc(4px);
$input-btn-padding-x-sm: rem-calc(8px);
$input-btn-padding-y-sm: rem-calc(4px);
$input-btn-padding-x-sm: rem-calc(8px);
$input-btn-padding-y-lg: rem-calc(14px);
$input-btn-padding-x-lg: rem-calc(20px);
$input-btn-padding-y-lg: rem-calc(14px);
$input-btn-padding-x-lg: rem-calc(20px);
$input-height: rem-calc(50px);

View File

@@ -1,3 +1,21 @@
$grid-columns: 12;
$grid-gutter-width: rem-calc(20px);
$grid-row-columns: 6;
// Made this more like the breakpoints used in Bootstrap 5
$grid-breakpoints: (
xs: 0,
sm: 576px,
md: 768px,
lg: 992px,
xl: 1200px,
xxl: 1400px,
);
$container-max-widths: (
sm: 540px,
md: 720px,
lg: 960px,
xl: 1140px,
xxl: 1320px,
);
$grid-columns: 12;
$grid-gutter-width: rem-calc(20px);
$grid-row-columns: 6;

View File

@@ -1,39 +1,41 @@
$font-family-sans-serif: "Roboto", -apple-system, blinkmacsystemfont, "Segoe UI", roboto, "Helvetica Neue", arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
$font-family-base: $font-family-sans-serif;
$font-family-sans-serif: "Plus Jakarta Sans", sans-serif;
$font-family-base: $font-family-sans-serif;
$headings-font-family: $font-family-sans-serif;
$font-weight-base: 400;
$font-size-base: rem-calc(16px);
$font-size-lg: $font-size-base * 1.125;
$font-size-sm: $font-size-base * .875;
$font-size-xs: $font-size-base * .6875;
$font-size-base: rem-calc(14px);
$font-size-lg: $font-size-base * 1.125;
$font-size-sm: $font-size-base * 0.875;
$font-size-xs: $font-size-base * 0.6875;
$font-weight-lighter: 200;
$font-weight-light: 300;
$font-weight-normal: 400;
$font-weight-bold: 700;
$font-weight-bolder: 800;
$font-weight-lighter: 200;
$font-weight-light: 300;
$font-weight-normal: 400;
$font-weight-bold: 700;
$font-weight-bolder: 800;
$h1-font-size: $font-size-base * 2;
$h2-font-size: $font-size-base * 1.75;
$h3-font-size: $font-size-base * 1.5;
$h4-font-size: $font-size-base * 1.25;
$h5-font-size: $font-size-base * 1.125;
$h6-font-size: $font-size-base;
$h1-font-size: $font-size-base * 2;
$h2-font-size: $font-size-base * 1.75;
$h3-font-size: $font-size-base * 1.5;
$h4-font-size: $font-size-base * 1.25;
$h5-font-size: $font-size-base * 1.125;
$h6-font-size: $font-size-base;
$display1-size: $font-size-base * 2.5;
$display2-size: $font-size-base * 2.25;
$display3-size: $font-size-base * 2;
$display4-size: $font-size-base * 1.75;
$display1-size: $font-size-base * 2.5;
$display2-size: $font-size-base * 2.25;
$display3-size: $font-size-base * 2;
$display4-size: $font-size-base * 1.75;
$display1-weight: 400;
$display2-weight: 400;
$display3-weight: 400;
$display4-weight: 400;
$display1-weight: 400;
$display2-weight: 400;
$display3-weight: 400;
$display4-weight: 400;
$text-muted: $gray-600;
$text-muted: $gray-600;
$paragraph-margin-bottom: rem-calc(20px);
$paragraph-margin-bottom: rem-calc(20px);
$headings-margin-bottom: rem-calc(20px);
$headings-font-weight: 700;
$headings-line-height: 1.2;
$headings-color: $gray-900;
$headings-margin-bottom: rem-calc(20px);
$headings-font-weight: 700;
$headings-line-height: 1.2;
$headings-color: $gray-900;

View File

@@ -1,19 +0,0 @@
// 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,3 +1,3 @@
@import "abstracts/index";
@import "theme/index";
@import "custom";
@import "theme/custom/custom";

View File

@@ -1,83 +1,80 @@
@use "sass:map";
.customer-links {
margin: 0 0 map.get($spacers, 3);
@include media-breakpoint-up(lg) {
margin: 0;
}
.link-item {
display: flex;
align-items: center;
}
&__list {
@include media-breakpoint-down(md) {
display: flex;
flex-wrap: nowrap;
margin: rem-calc(20px) 0 0;
overflow-x: scroll;
overflow-y: hidden;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
-ms-overflow-style: -ms-autohiding-scrollbar;
&::-webkit-scrollbar {
display: none;
}
}
}
a {
display: block;
flex: 0 0;
max-width: inherit;
padding: map.get($spacers, 2);
font-weight: 700;
color: $gray-800;
white-space: nowrap;
border-radius: $border-radius;
@include font-size($font-size-base);
margin: 0 0 map.get($spacers, 3);
@include media-breakpoint-up(lg) {
white-space: normal;
margin: 0;
}
@include hover-focus() {
color: $primary;
text-decoration: none;
.link-item {
display: flex;
align-items: center;
}
i {
margin-right: map.get($spacers, 1);
color: $primary;
@include font-size(26px);
@include media-breakpoint-up(md) {
margin-right: map.get($spacers, 2);
}
&__list {
@include media-breakpoint-down(md) {
display: flex;
flex-wrap: nowrap;
margin: rem-calc(20px) 0 0;
overflow-x: scroll;
overflow-y: hidden;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
-ms-overflow-style: -ms-autohiding-scrollbar;
&::-webkit-scrollbar {
display: none;
}
}
}
&.active {
color: #fff;
background: $primary;
&::after {
a {
display: block;
}
flex: 0 0;
max-width: inherit;
padding: map.get($spacers, 2);
font-weight: 700;
white-space: nowrap;
border-radius: $border-radius;
@include font-size($font-size-base);
i {
color: inherit;
}
@include media-breakpoint-up(lg) {
white-space: normal;
}
@include hover-focus() {
text-decoration: none;
}
i {
margin-right: map.get($spacers, 1);
@include font-size(26px);
@include media-breakpoint-up(md) {
margin-right: map.get($spacers, 2);
}
}
&.active {
color: #fff;
background: $primary;
&::after {
display: block;
}
i {
color: inherit;
}
}
}
}
&__logout {
text-align: center;
&__logout {
text-align: center;
&::after,
&::before {
display: none;
&::after,
&::before {
display: none;
}
}
}
}

View File

@@ -1,22 +1,32 @@
/* This is the main custom SCSS file for the Falcon theme.
/*
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 */
NOTE: All bootstrap overrides have been configured under themes/falcon/_dev/css/abstracts/variables/bootstrap
Look up all available Bootstrap variables here: https://rstudio.github.io/bslib/articles/bs5-variables/index.html
*/
//Abstracts: Things used throughout the site such as utility classes and generic overrides.
//@import "abstracts/mixins";
//@import "abstracts/base";
@import "abstracts/functions";
@import "abstracts/mixins";
@import "abstracts/utilities";
// Components: parts of the theme itself that are not associated with a module.
//@import "components/buttons";
// Modules: Styling for specific modules.
//@import "modules/";
@import "components/buttons";
@import "components/forms";
//Layouts: Parts of a page, such as the header, footer, etc.
//@import "layout/footer";
//@import "layout/header";
@import "layout/header";
@import "layout/left-column";
@import "layout/swiper";
// Pages
//@import "pages/checkout";
//@import "pages/home";
//@import "pages/listing";
//@import "pages/product";

View File

@@ -0,0 +1,41 @@
@use "sass:math";
// Font size utility classes generator
// Generates utility classes like .fs-14, .fs-16, etc.
@for $i from 8 through 72 {
.fs-#{$i} {
font-size: rem-calc($i * 1px) !important;
}
}
// Font weight utility classes generator
// Generates utility classes like .fw-100, .fw-200, etc.
@for $i from 100 through 900 {
@if $i % 100 == 0 {
.fw-#{$i} {
font-weight: #{$i} !important;
}
}
}
// gap size utility classes generator
// Generates utility classes like .gap-4, .gap-col-4, .gap-row-4
@for $i from 1 through 35 {
.gap-#{$i} {
gap: rem-calc($i * 1px) !important;
}
.gap-col-#{$i} {
column-gap: rem-calc($i * 1px) !important;
}
.gap-row-#{$i} {
row-gap: rem-calc($i * 1px) !important;
}
}
// hex to rgba function
@function hex-to-rgba($hex, $alpha) {
$r: red($hex);
$g: green($hex);
$b: blue($hex);
@return rgba($r, $g, $b, $alpha);
}

View File

@@ -0,0 +1,73 @@
// Bootstrap 5-style responsive mixins (more intuitive than Bootstrap 4)
// Learn the difference here https://getbootstrap.com/docs/5.0/migration/#sass
@mixin bs5-media-breakpoint-up($name) {
$min: map-get($grid-breakpoints, $name);
@if $min and $min > 0 {
@media (min-width: $min) {
@content;
}
} @else {
@content;
}
}
@mixin bs5-media-breakpoint-down($name) {
$max: map-get($grid-breakpoints, $name);
@if $max {
@media (max-width: calc(#{$max} - 0.02px)) {
@content;
}
} @else {
@content;
}
}
@mixin bs5-media-breakpoint-between($lower, $upper) {
$min: map-get($grid-breakpoints, $lower);
$max: map-get($grid-breakpoints, $upper);
@if $min and $max {
@media (min-width: $min) and (max-width: calc(#{$max} - 0.02px)) {
@content;
}
}
}
@mixin bs5-media-breakpoint-only($name) {
$breakpoint-names: map-keys($grid-breakpoints);
$index: index($breakpoint-names, $name);
$next-name: nth($breakpoint-names, $index + 1);
@include bs5-media-breakpoint-between($name, $next-name) {
@content;
}
}
// Mixin to add an after pseudo-element with a mask image to customize button colors
/* Example:
.btn-arrow-right{
&::after {
@include btn-after("../img/arrow-right.svg");
}
}
*/
@mixin btn-after($url, $color: currentColor) {
content: "";
display: inline-block;
width: 1em;
height: 1em;
margin-left: 0.5em;
vertical-align: middle;
background-color: currentColor;
mask-image: url(#{$url});
mask-size: contain;
mask-repeat: no-repeat;
mask-position: center;
-webkit-mask-image: url(#{$url});
-webkit-mask-size: contain;
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
}

View File

@@ -0,0 +1,43 @@
// Header selector for interpolation
// Example usage: #{$headers} { ... }
$headers: "h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6";
// All text selector for interpolation
$text: "#{$headers}, p, span, a, li";
// Section Spacers. Just set the two variables below and it is fully responsive!
// $section-spacer-small applies on breakpoints lg and below, OR always if specified in the class with --small (ex. section-spacer--small).
$section-spacer: rem-calc(50px);
$section-spacer-small: rem-calc(25px);
.section-spacer-both {
margin-top: $section-spacer-small;
margin-bottom: $section-spacer-small;
@include bs5-media-breakpoint-up(lg) {
margin-top: $section-spacer;
margin-bottom: $section-spacer;
}
&--small {
margin-top: $section-spacer-small;
margin-bottom: $section-spacer-small;
@include bs5-media-breakpoint-up(lg) {
margin-top: $section-spacer-small;
margin-bottom: $section-spacer-small;
}
}
}
.section-spacer {
@extend .section-spacer-both;
margin-bottom: unset;
&--small {
margin-bottom: unset;
}
}
.section-spacer-bottom {
@extend .section-spacer-both;
margin-top: unset;
&--small {
margin-top: unset;
}
}

View File

@@ -0,0 +1,57 @@
// To customize text colors per button, use this map and below function
$btn-color: (
"primary": $white,
"secondary": $white,
"light": $primary,
"dark": $white,
);
@each $name, $color in $btn-color {
.btn-#{$name},
.btn-lg-#{$name},
.btn-sm-#{$name} {
color: $color !important;
}
}
// Configure btn-outline text color on hover per button. (Same thing as above but for btn-outline and only on hover)
$btn-outline-hover: (
"primary": $white,
"secondary": $white,
);
@each $name, $bg in $btn-outline-hover {
$text-color: if(
map-has-key($btn-color, $name),
map-get($btn-color, $name),
$white
);
.btn-outline-#{$name}:hover,
.btn-outline-#{$name}:focus,
.btn-outline-#{$name}:active,
.btn-outline-#{$name}.active,
.show > .btn-outline-#{$name}.dropdown-toggle {
color: $text-color !important;
}
}
// Bootstrap touchspin
.bootstrap-touchspin {
flex-wrap: nowrap;
.input-touchspin {
height: rem-calc(50px);
min-width: rem-calc(75px);
border-left: 0px;
}
.input-group-btn-vertical {
display: flex;
flex-direction: column;
button {
border-radius: 0px;
}
}
}

View File

@@ -0,0 +1,10 @@
.input-group.js-parent-focus {
.input-group-append .btn {
border-top-right-radius: $border-radius !important;
border-bottom-right-radius: $border-radius !important;
}
}
.input-group.js-parent-focus {
height: $input-height;
}

View File

@@ -0,0 +1,17 @@
.header-top__row {
flex-wrap: wrap;
.header-top__block--search {
@include media-breakpoint-down(sm) {
order: 1;
}
.js-search-form {
max-width: unset;
width: 100%;
margin: 0;
}
}
.header-top__badge {
right: -0.9em;
}
}

View File

@@ -0,0 +1,10 @@
#left-column {
.list-group-item,
.list-group-item-action-dropdown-link,
label {
font-family: $font-family-base;
font-size: $font-size-base;
color: $secondary;
font-weight: 400;
}
}

View File

@@ -0,0 +1,3 @@
.swiper-slide {
height: auto;
}

View File

@@ -1,6 +1,6 @@
import $ from 'jquery';
import prestashop from 'prestashop';
import debounce from '@js/theme/utils/debounce';
import $ from "jquery";
import prestashop from "prestashop";
import debounce from "@js/theme/utils/debounce";
prestashop.cart = prestashop.cart || {};
@@ -9,358 +9,395 @@ prestashop.cart.active_inputs = null;
const spinnerSelector = 'input[name="product-quantity-spin"]';
let hasError = false;
let isUpdateOperation = false;
let errorMsg = '';
let errorMsg = "";
const CheckUpdateQuantityOperations = {
switchErrorStat: () => {
/**
* if errorMsg is not empty or if notifications are shown, we have error to display
* if hasError is true, quantity was not updated : we don't disable checkout button
*/
const $checkoutBtn = $(prestashop.themeSelectors.checkout.btn);
switchErrorStat: () => {
/**
* if errorMsg is not empty or if notifications are shown, we have error to display
* if hasError is true, quantity was not updated : we don't disable checkout button
*/
const $checkoutBtn = $(prestashop.themeSelectors.checkout.btn);
if ($(prestashop.themeSelectors.notifications.dangerAlert).length || (errorMsg !== '' && !hasError)) {
$checkoutBtn.addClass('disabled');
}
if (
$(prestashop.themeSelectors.notifications.dangerAlert).length ||
(errorMsg !== "" && !hasError)
) {
$checkoutBtn.addClass("disabled");
}
if (errorMsg !== '') {
const strError = `
if (errorMsg !== "") {
const strError = `
<article class="alert alert-danger" role="alert" data-alert="danger">
<ul class="mb-0">
<li>${errorMsg}</li>
</ul>
</article>
`;
$(prestashop.themeSelectors.notifications.container).html(strError);
errorMsg = '';
isUpdateOperation = false;
if (hasError) {
// if hasError is true, quantity was not updated : allow checkout
$checkoutBtn.removeClass('disabled');
}
} else if (!hasError && isUpdateOperation) {
hasError = false;
isUpdateOperation = false;
$(prestashop.themeSelectors.notifications.container).html('');
$checkoutBtn.removeClass('disabled');
}
},
checkUpdateOperation: (resp) => {
/**
* resp.hasError can be not defined but resp.errors not empty: quantity is updated but order cannot be placed
* when resp.hasError=true, quantity is not updated
*/
const { hasError: hasErrorOccurred, errors: errorData } = resp;
hasError = hasErrorOccurred ?? false;
const errors = errorData ?? '';
$(prestashop.themeSelectors.notifications.container).html(strError);
errorMsg = "";
isUpdateOperation = false;
if (hasError) {
// if hasError is true, quantity was not updated : allow checkout
$checkoutBtn.removeClass("disabled");
}
} else if (!hasError && isUpdateOperation) {
hasError = false;
isUpdateOperation = false;
$(prestashop.themeSelectors.notifications.container).html("");
$checkoutBtn.removeClass("disabled");
}
},
checkUpdateOperation: (resp) => {
/**
* resp.hasError can be not defined but resp.errors not empty: quantity is updated but order cannot be placed
* when resp.hasError=true, quantity is not updated
*/
const { hasError: hasErrorOccurred, errors: errorData } = resp;
hasError = hasErrorOccurred ?? false;
const errors = errorData ?? "";
// 1.7.2.x returns errors as string, 1.7.3.x returns array
if (errors instanceof Array) {
errorMsg = errors.join(' ');
} else {
errorMsg = errors;
}
// 1.7.2.x returns errors as string, 1.7.3.x returns array
if (errors instanceof Array) {
errorMsg = errors.join(" ");
} else {
errorMsg = errors;
}
isUpdateOperation = true;
},
isUpdateOperation = true;
},
};
/**
* Attach Bootstrap TouchSpin event handlers
*/
function createSpin() {
$.each($(spinnerSelector), (index, spinner) => {
$(spinner).TouchSpin({
verticalupclass: 'material-icons touchspin-up',
verticaldownclass: 'material-icons touchspin-down',
buttondown_class: 'btn btn-touchspin js-touchspin js-increase-product-quantity',
buttonup_class: 'btn btn-touchspin js-touchspin js-decrease-product-quantity',
min: parseInt($(spinner).attr('min'), 10),
max: 1000000,
$.each($(spinnerSelector), (index, spinner) => {
$(spinner).TouchSpin({
verticalupclass: "",
verticaldownclass: "",
buttondown_class:
"btn btn-primary btn-touchspin js-touchspin js-increase-product-quantity",
buttonup_class:
"btn btn-primary btn-touchspin js-touchspin js-decrease-product-quantity",
min: parseInt($(spinner).attr("min"), 10),
max: 1000000,
verticalup:
'<img src="/themes/falcon/_dev/img/chevron_up.svg" alt="Up" />',
verticaldown:
'<img src="/themes/falcon/_dev/img/chevron_down.svg" alt="Down" />',
});
});
});
CheckUpdateQuantityOperations.switchErrorStat();
CheckUpdateQuantityOperations.switchErrorStat();
}
const preventCustomModalOpen = (event) => {
if (window.shouldPreventModal) {
event.preventDefault();
if (window.shouldPreventModal) {
event.preventDefault();
return false;
}
return false;
}
return true;
return true;
};
$(() => {
const productLineInCartSelector = prestashop.themeSelectors.cart.productLineQty;
const promises = [];
const productLineInCartSelector =
prestashop.themeSelectors.cart.productLineQty;
const promises = [];
prestashop.on('updateCart', () => {
$(prestashop.themeSelectors.cart.quickview).modal('hide');
$('body').addClass('cart-loading');
});
prestashop.on("updateCart", () => {
$(prestashop.themeSelectors.cart.quickview).modal("hide");
$("body").addClass("cart-loading");
});
prestashop.on('updatedCart', () => {
window.shouldPreventModal = false;
prestashop.on("updatedCart", () => {
window.shouldPreventModal = false;
$(prestashop.themeSelectors.product.customizationModal).on('show.bs.modal', (modalEvent) => {
preventCustomModalOpen(modalEvent);
$(prestashop.themeSelectors.product.customizationModal).on(
"show.bs.modal",
(modalEvent) => {
preventCustomModalOpen(modalEvent);
},
);
createSpin();
$("body").removeClass("cart-loading");
});
createSpin();
$('body').removeClass('cart-loading');
});
createSpin();
const $body = $("body");
const $body = $('body');
function isTouchSpin(namespace) {
return namespace === 'on.startupspin' || namespace === 'on.startdownspin';
}
function shouldIncreaseProductQuantity(namespace) {
return namespace === 'on.startupspin';
}
function findCartLineProductQuantityInput($target) {
const $input = $target.parents(prestashop.themeSelectors.cart.touchspin).find(productLineInCartSelector);
if ($input.is(':focus')) {
return null;
function isTouchSpin(namespace) {
return (
namespace === "on.startupspin" || namespace === "on.startdownspin"
);
}
return $input;
}
function camelize(subject) {
const actionTypeParts = subject.split('-');
let i;
let part;
let camelizedSubject = '';
for (i = 0; i < actionTypeParts.length; i += 1) {
part = actionTypeParts[i];
if (i !== 0) {
part = part.substring(0, 1).toUpperCase() + part.substring(1);
}
camelizedSubject += part;
function shouldIncreaseProductQuantity(namespace) {
return namespace === "on.startupspin";
}
return camelizedSubject;
}
function findCartLineProductQuantityInput($target) {
const $input = $target
.parents(prestashop.themeSelectors.cart.touchspin)
.find(productLineInCartSelector);
function parseCartAction($target, namespace) {
if (!isTouchSpin(namespace)) {
return {
url: $target.attr('href'),
type: camelize($target.data('link-action')),
};
if ($input.is(":focus")) {
return null;
}
return $input;
}
const $input = findCartLineProductQuantityInput($target);
function camelize(subject) {
const actionTypeParts = subject.split("-");
let i;
let part;
let camelizedSubject = "";
let cartAction = {};
for (i = 0; i < actionTypeParts.length; i += 1) {
part = actionTypeParts[i];
if ($input) {
if (shouldIncreaseProductQuantity(namespace)) {
cartAction = {
url: $input.data('up-url'),
type: 'increaseProductQuantity',
};
} else {
cartAction = {
url: $input.data('down-url'),
type: 'decreaseProductQuantity',
};
}
if (i !== 0) {
part = part.substring(0, 1).toUpperCase() + part.substring(1);
}
camelizedSubject += part;
}
return camelizedSubject;
}
return cartAction;
}
function parseCartAction($target, namespace) {
if (!isTouchSpin(namespace)) {
return {
url: $target.attr("href"),
type: camelize($target.data("link-action")),
};
}
const abortPreviousRequests = () => {
let promise;
while (promises.length > 0) {
promise = promises.pop();
promise.abort();
const $input = findCartLineProductQuantityInput($target);
let cartAction = {};
if ($input) {
if (shouldIncreaseProductQuantity(namespace)) {
cartAction = {
url: $input.data("up-url"),
type: "increaseProductQuantity",
};
} else {
cartAction = {
url: $input.data("down-url"),
type: "decreaseProductQuantity",
};
}
}
return cartAction;
}
};
const getTouchSpinInput = ($button) => $($button.parents(prestashop.themeSelectors.cart.touchspin).find('input'));
$(prestashop.themeSelectors.product.customizationModal).on('show.bs.modal', (modalEvent) => {
preventCustomModalOpen(modalEvent);
});
const handleCartAction = (event) => {
event.preventDefault();
window.shouldPreventModal = true;
const $target = $(event.currentTarget);
const { dataset } = event.currentTarget;
const cartAction = parseCartAction($target, event.namespace);
const requestData = {
ajax: '1',
action: 'update',
const abortPreviousRequests = () => {
let promise;
while (promises.length > 0) {
promise = promises.pop();
promise.abort();
}
};
if (typeof cartAction === 'undefined') {
return;
}
const getTouchSpinInput = ($button) =>
$(
$button
.parents(prestashop.themeSelectors.cart.touchspin)
.find("input"),
);
$.ajax({
url: cartAction.url,
method: 'POST',
data: requestData,
dataType: 'json',
beforeSend: (jqXHR) => {
promises.push(jqXHR);
},
})
.then((resp) => {
const $quantityInput = getTouchSpinInput($target);
CheckUpdateQuantityOperations.checkUpdateOperation(resp);
$quantityInput.val(resp.quantity);
$(prestashop.themeSelectors.product.customizationModal).on(
"show.bs.modal",
(modalEvent) => {
preventCustomModalOpen(modalEvent);
},
);
// Refresh cart preview
prestashop.emit('updateCart', {
reason: dataset,
resp,
});
})
.fail((resp) => {
prestashop.emit('handleError', {
eventType: 'updateProductInCart',
resp,
cartAction: cartAction.type,
});
});
};
const handleCartAction = (event) => {
event.preventDefault();
window.shouldPreventModal = true;
$body.on('click', prestashop.themeSelectors.cart.actions, handleCartAction);
const $target = $(event.currentTarget);
const { dataset } = event.currentTarget;
function sendUpdateQuantityInCartRequest(updateQuantityInCartUrl, requestData, $target) {
abortPreviousRequests();
window.shouldPreventModal = true;
const cartAction = parseCartAction($target, event.namespace);
const requestData = {
ajax: "1",
action: "update",
};
return $.ajax({
url: updateQuantityInCartUrl,
method: 'POST',
data: requestData,
dataType: 'json',
beforeSend: (jqXHR) => {
promises.push(jqXHR);
},
})
.then((resp) => {
CheckUpdateQuantityOperations.checkUpdateOperation(resp);
if (typeof cartAction === "undefined") {
return;
}
$target.val(resp.quantity);
const dataset = ($target && $target.dataset) ? $target.dataset : resp;
$.ajax({
url: cartAction.url,
method: "POST",
data: requestData,
dataType: "json",
beforeSend: (jqXHR) => {
promises.push(jqXHR);
},
})
.then((resp) => {
const $quantityInput = getTouchSpinInput($target);
CheckUpdateQuantityOperations.checkUpdateOperation(resp);
$quantityInput.val(resp.quantity);
// Refresh cart preview
prestashop.emit('updateCart', {
reason: dataset,
resp,
});
})
.fail((resp) => {
prestashop.emit('handleError', {
eventType: 'updateProductQuantityInCart',
resp,
});
});
}
function getQuantityChangeType($quantity) {
return $quantity > 0 ? 'up' : 'down';
}
function getRequestData(quantity) {
return {
ajax: '1',
qty: Math.abs(quantity),
action: 'update',
op: getQuantityChangeType(quantity),
// Refresh cart preview
prestashop.emit("updateCart", {
reason: dataset,
resp,
});
})
.fail((resp) => {
prestashop.emit("handleError", {
eventType: "updateProductInCart",
resp,
cartAction: cartAction.type,
});
});
};
}
function updateProductQuantityInCart(event) {
const $target = $(event.currentTarget);
const updateQuantityInCartUrl = $target.data('update-url');
const baseValue = $target.attr('value');
$body.on("click", prestashop.themeSelectors.cart.actions, handleCartAction);
// There should be a valid product quantity in cart
const targetValue = $target.val();
function sendUpdateQuantityInCartRequest(
updateQuantityInCartUrl,
requestData,
$target,
) {
abortPreviousRequests();
window.shouldPreventModal = true;
/* eslint-disable */
if (targetValue != parseInt(targetValue, 10) || targetValue < 0 || Number.isNaN(targetValue)) {
window.shouldPreventModal = false;
$target.val(baseValue);
return;
}
/* eslint-enable */
return $.ajax({
url: updateQuantityInCartUrl,
method: "POST",
data: requestData,
dataType: "json",
beforeSend: (jqXHR) => {
promises.push(jqXHR);
},
})
.then((resp) => {
CheckUpdateQuantityOperations.checkUpdateOperation(resp);
// There should be a new product quantity in cart
const qty = targetValue - baseValue;
$target.val(resp.quantity);
const dataset =
$target && $target.dataset ? $target.dataset : resp;
if (qty === 0) {
return;
// Refresh cart preview
prestashop.emit("updateCart", {
reason: dataset,
resp,
});
})
.fail((resp) => {
prestashop.emit("handleError", {
eventType: "updateProductQuantityInCart",
resp,
});
});
}
if (targetValue === '0') {
$target.closest('.product-line-actions').find('[data-link-action="delete-from-cart"]').click();
} else {
$target.attr('value', targetValue);
sendUpdateQuantityInCartRequest(updateQuantityInCartUrl, getRequestData(qty), $target);
function getQuantityChangeType($quantity) {
return $quantity > 0 ? "up" : "down";
}
}
$body.on('touchspin.on.stopspin', spinnerSelector, debounce(updateProductQuantityInCart));
function getRequestData(quantity) {
return {
ajax: "1",
qty: Math.abs(quantity),
action: "update",
op: getQuantityChangeType(quantity),
};
}
$body.on(
'focusout keyup',
productLineInCartSelector,
(event) => {
if (event.type === 'keyup') {
if (event.keyCode === 13) {
isUpdateOperation = true;
updateProductQuantityInCart(event);
function updateProductQuantityInCart(event) {
const $target = $(event.currentTarget);
const updateQuantityInCartUrl = $target.data("update-url");
const baseValue = $target.attr("value");
// There should be a valid product quantity in cart
const targetValue = $target.val();
/* eslint-disable */
if (
targetValue != parseInt(targetValue, 10) ||
targetValue < 0 ||
Number.isNaN(targetValue)
) {
window.shouldPreventModal = false;
$target.val(baseValue);
return;
}
/* eslint-enable */
// There should be a new product quantity in cart
const qty = targetValue - baseValue;
if (qty === 0) {
return;
}
if (targetValue === "0") {
$target
.closest(".product-line-actions")
.find('[data-link-action="delete-from-cart"]')
.click();
} else {
$target.attr("value", targetValue);
sendUpdateQuantityInCartRequest(
updateQuantityInCartUrl,
getRequestData(qty),
$target,
);
}
}
$body.on(
"touchspin.on.stopspin",
spinnerSelector,
debounce(updateProductQuantityInCart),
);
$body.on("focusout keyup", productLineInCartSelector, (event) => {
if (event.type === "keyup") {
if (event.keyCode === 13) {
isUpdateOperation = true;
updateProductQuantityInCart(event);
}
return false;
}
if (!isUpdateOperation) {
updateProductQuantityInCart(event);
}
return false;
}
});
if (!isUpdateOperation) {
updateProductQuantityInCart(event);
}
$body.on("click", prestashop.themeSelectors.cart.discountCode, (event) => {
event.stopPropagation();
event.preventDefault();
return false;
},
);
const $code = $(event.currentTarget);
const $discountInput = $("[name=discount_name]");
const $discountForm = $discountInput.closest("form");
$body.on(
'click',
prestashop.themeSelectors.cart.discountCode,
(event) => {
event.stopPropagation();
event.preventDefault();
$discountInput.val($code.text());
// Show promo code field
$discountForm.trigger("submit");
const $code = $(event.currentTarget);
const $discountInput = $('[name=discount_name]');
const $discountForm = $discountInput.closest('form');
$discountInput.val($code.text());
// Show promo code field
$discountForm.trigger('submit');
return false;
},
);
return false;
});
});

View File

@@ -66,6 +66,22 @@ function initStickyHeader() {
}
}
// Sync product thumbs height with main images height
function syncThumbsHeight() {
const mainImages = document.querySelector(".product-main-images");
const thumbs = document.querySelector(".product-thumbs.swiper");
// Only apply if Swiper is vertical
if (mainImages && thumbs && thumbs.classList.contains("swiper-vertical")) {
thumbs.style.maxHeight = mainImages.offsetHeight + "px";
thumbs.style.height = mainImages.offsetHeight + "px";
} else {
return;
}
}
window.addEventListener("load", syncThumbsHeight);
window.addEventListener("resize", syncThumbsHeight);
$(() => {
initStickyHeader();
accLinksTriggerActive();
@@ -79,4 +95,19 @@ $(() => {
$(".js-select-link").on("change", ({ target }) => {
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

@@ -1,5 +1,5 @@
<div class="col-auto mt-2 px-1">
<div class="col-auto">
<a
class="product-page__action-btn btn btn-light shadow rounded-circle favorite-btn p-2"
href="#"

View File

@@ -33,6 +33,17 @@
title="{l s='Log in to your customer account' d='Shop.Theme.Customeraccount'}"
{/if}
>
<span class="d-none d-xl-block text-secondary">
{if $logged}
{if $customer.firstname}
{$customer.firstname|escape:'html':'UTF-8'}
{else}
{l s='Log out' d='Shop.Theme.Actions'}
{/if}
{else}
{l s='Log in' d='Shop.Theme.Actions'}
{/if}
</span>
<div class="header-top__icon-container">
{capture name="svg_output"}{svg_icon file='person.svg'}{/capture}
{if $smarty.capture.svg_output}

View File

@@ -23,8 +23,27 @@
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
*}
{* Simple display without titles for the header and footer *}
{if $hookName == "displayFooterPrivacy"}
{foreach $linkBlocks as $linkBlock}
{foreach $linkBlock.links as $link}
<p class="links-list__elem mb-0">
<a
id="{$link.id}-{$linkBlock.id}"
class="{$link.class} links-list__link"
href="{$link.url}"
title="{$link.description}"
{if !empty($link.target)} target="{$link.target}" {/if}
>
{$link.title}
</a>
</p>
{/foreach}
{/foreach}
{else}
{* Regular display *}
{foreach $linkBlocks as $linkBlock}
<div class="col-md-3 col-12 mb-lg-4">
<div class="col-12 col-md-3">
{assign var=_expand_id value=10|mt_rand:100000}
<div class="d-flex align-items-center mb-3 justify-content-between position-relative">
<span class="h4 mb-0">{$linkBlock.title}</span>
@@ -57,3 +76,4 @@
</div>
</div>
{/foreach}
{/if}

View File

@@ -0,0 +1,22 @@
<div class="footer-bottom py-1 py-lg-3">
<div class="container">
<div class="row d-flex flex-column flex-lg-row justify-content-between align-items-center">
<div class="copyright">
<span class="text-white fw-400">
{l
s='Copyright © %year% %shop%'
sprintf=['%year%' => $smarty.now|date_format:"%Y", '%shop%' => $shop.name|capitalize]
d='Shop.Theme.Footer'
}
</span>
</div>
<a href="https://dewebsmid.nl/prestashop/" class="text-white fw-400" rel="noopener" target="_blank">
{l s='PrestaShop' d='Shop.Theme.Footer'}
</a>
<div class="privacy d-flex gap-20">
{* Need to create this hook in the db. Use ps_linklist with it- see modules/ps_linklist/views/templates/hook/linkblock.tpl *}
{hook h='displayFooterPrivacy'}
</div>
</div>
</div>
</div>

View File

@@ -29,7 +29,7 @@
{/block}
</div>
</div>
<div class="footer-container">
<div class="footer-container pb-0 mt-0">
<div class="container">
<div class="row">
{block name='hook_footer'}
@@ -42,4 +42,5 @@
{/block}
</div>
</div>
{include file='_partials/footer-copyright.tpl'}
</div>

View File

@@ -74,9 +74,13 @@
{/block}
{block name='stylesheets'}
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&display=swap" rel="stylesheet">
{include file="_partials/stylesheets.tpl" stylesheets=$stylesheets}
{/block}
{block name='javascript_head'}
{include file="_partials/javascript.tpl" javascript=$javascript.head vars=$js_custom_vars}
{/block}

View File

@@ -29,24 +29,14 @@
{/block}
{block name='header_nav'}
<nav class="header-nav border-bottom bg-light py-1 d-none d-md-block">
<div class="container">
<div class="row align-items-center">
{hook h='displayNav1'}
{hook h='displayNav2'}
</div>
</div>
</nav>
{/block}
{block name='header_top'}
<div class="js-header-top-wrapper">
<div class="header-top js-header-top">
<div class="header-top__content pt-md-3 pb-md-0 py-2">
<div class="header-top__content pb-md-0 py-2">
<div class="container">
<div class="row header-top__row">
<div class="col flex-grow-0 header-top__block header-top__block--menu-toggle d-block d-md-none">
@@ -58,12 +48,17 @@
data-target="#mobile_top_menu_wrapper"
>
<div class="header-top__icon-container">
<span class="header-top__icon material-icons">menu</span>
{capture name="svg_output"}{svg_icon file='hamburger-menu.svg'}{/capture}
{if $smarty.capture.svg_output}
{$smarty.capture.svg_output nofilter}
{else}
<span class="header-top__icon material-icons">menu</span>
{/if}
</div>
</a>
</div>
<div class="col-md-4 col header-top__block header-top__block--logo">
<div class="col col-md-auto col header-top__block header-top__block--logo">
<a href="{$urls.pages.index}">
{images_block webpEnabled=$webpEnabled}
<img
@@ -79,13 +74,15 @@
{/images_block}
</a>
</div>
{hook h='displayTop'}
</div>
<div class="row">
<div class="col">
{hook h='displayNavFullWidth'}
</div>
</div>
</div>
</div>
</div>
</div>
{hook h='displayNavFullWidth'}
{/block}
{/block}

View File

@@ -26,38 +26,43 @@
{block name='pagination_page_list'}
{if $pagination.should_be_displayed}
<nav>
<ul class="pagination justify-content-center mt-4 mb-2">
{foreach from=$pagination.pages item="page"}
<li class="page-item{if $page.current} active{/if} {if $page.type === 'spacer'}disabled{/if}">
{if $page.type === 'spacer'}
<span
rel="{if $page.type === 'previous'}prev{elseif $page.type === 'next'}next{else}nofollow{/if}"
href="#"
class="page-link"
>
&hellip;
</span>
{else}
<a
rel="{if $page.type === 'previous'}prev{elseif $page.type === 'next'}next{else}nofollow{/if}"
href="{$page.url}"
class="page-link {['disabled' => !$page.clickable, 'js-search-link' => true]|classnames}"
>
{if $page.type === 'previous'}
<span class="material-icons font-reset align-middle">keyboard_arrow_left</span>
<span class="sr-only">{l s='Previous' d='Shop.Theme.Actions'}</span>
{elseif $page.type === 'next'}
<span class="material-icons font-reset align-middle">keyboard_arrow_right</span>
<span class="sr-only">{l s='Next' d='Shop.Theme.Actions'}</span>
{else}
{$page.page}
{/if}
</a>
{/if}
</li>
{/foreach}
</ul>
</nav>
<div class="d-flex justify-content-between align-items-center">
<div class="results__block">
{l s='%curr_numer% van %total_items% resultaten' sprintf=['%curr_numer%' => $pagination.items_shown_to,'%total_items%' => $pagination.total_items] d='Shop.Theme.Actions'}
</div>
<nav>
<ul class="pagination justify-content-center m-0">
{foreach from=$pagination.pages item="page"}
<li class="page-item{if $page.current} active{/if} {if $page.type === 'spacer'}disabled{/if}">
{if $page.type === 'spacer'}
<span
rel="{if $page.type === 'previous'}prev{elseif $page.type === 'next'}next{else}nofollow{/if}"
href="#"
class="page-link"
>
&hellip;
</span>
{else}
<a
rel="{if $page.type === 'previous'}prev{elseif $page.type === 'next'}next{else}nofollow{/if}"
href="{$page.url}"
class="page-link {['disabled' => !$page.clickable, 'js-search-link' => true]|classnames}"
>
{if $page.type === 'previous'}
<span class="material-icons font-reset align-middle">keyboard_arrow_left</span>
<span class="sr-only">{l s='Previous' d='Shop.Theme.Actions'}</span>
{elseif $page.type === 'next'}
<span class="material-icons font-reset align-middle">keyboard_arrow_right</span>
<span class="sr-only">{l s='Next' d='Shop.Theme.Actions'}</span>
{else}
{$page.page}
{/if}
</a>
{/if}
</li>
{/foreach}
</ul>
</nav>
</div>
{/if}
{/block}

View File

@@ -1,7 +1,15 @@
{* 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">
{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">
{$category.additional_description nofilter}
</div>
{/if}
</div>
</div>

View File

@@ -23,24 +23,19 @@
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
*}
{block name='brand_miniature_item'}
<li class="col-lg-3 col-sm-4 col-6 mb-3">
<li class="col-12 col-sm-6 col-lg-4 col-xl-3 mb-3">
<div class="card h-100">
{$sizes = Image::getSize('home_default')}
{$brand_url = $brand.image.bySize.home_default.url}
<img
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='{$sizes.width}' height='{$sizes.height}' viewBox='0 0 1 1'%3E%3C/svg%3E"
data-src="{$brand.image|replace:'small_default':'home_default'}"
data-src="{$brand_url}"
alt="{$brand.name}"
class="card-img-top lazyload"
width="{$sizes.width}"
height="{$sizes.height}"
>
<div class="card-body">
<p class="h6 mb-0">
<a class="text-reset stretched-link" href="{$brand.url}">{$brand.name}</a>
</p>
</div>
<div class="card-footer text-center">
<span class="btn btn-link p-0">{$brand.nb_products}</span>
<a class="text-reset stretched-link" href="{$brand.url}">{$brand.name}</a>
</div>
</div>
</li>

View File

@@ -26,7 +26,7 @@
{$listingType = $type|default:'listing'}
<div
{if $listingType === 'listing'}
class="products-list__block products-list__block--grid"
class="products-list__block col col-sm-6 col-lg-4 col-xl-3"
{elseif $listingType === 'slider'}
class="swiper-slide product-slider__item col-6 col-md-4 col-lg-3"
{/if}

View File

@@ -26,8 +26,8 @@
{if !$configuration.is_catalog}
{block name='product_quantity'}
<div class="product-quantity row mb-1 mx-n1 mt-n2 align-items-center">
<div class="qty col-12 col-sm-auto mx-auto mt-2 px-1">
<div class="product-quantity row mb-1 align-items-center">
<div class="qty col-auto px-1">
<input
type="number"
name="qty"
@@ -46,7 +46,7 @@
>
</div>
<div class="add col mt-2 px-1">
<div class="add col">
<button
class="btn btn-primary add-to-cart btn-block"
data-button-action="add-to-cart"

View File

@@ -49,6 +49,9 @@
<div class="row product-container js-product-container">
<div class="col-md-5 mb-4">
{block name='page_header'}
<p class="h1 d-block d-md-none">{block name='page_title'}{$product.name}{/block}</p>
{/block}
{block name='page_content_container'}
{block name='page_content'}
<div class="position-relative">
@@ -64,7 +67,8 @@
<div class="col-md-7 mb-4">
{block name='page_header_container'}
{block name='page_header'}
<h1 class="h1">{block name='page_title'}{$product.name}{/block}</h1>
<h1 class="h1 d-none d-md-block">{block name='page_title'}{$product.name}{/block}</h1>
<p class="h5 d-block d-md-none">{block name='page_title'}{$product.name}{/block}</p>
{/block}
{/block}
{block name='product_prices'}

View File

@@ -130,6 +130,7 @@
value="{$product.quantity}"
name="product-quantity-spin"
min="{$product.minimal_quantity}"
data-verticalbuttons="true"
aria-label="{l s='%productName% product quantity field' sprintf=['%productName%' => $product.name] d='Shop.Theme.Checkout'}"
/>
</div>

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="value">{$cart.totals.total.value}</span>
</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}
{/block}

View File

@@ -72,7 +72,7 @@
{/if}
<div class="add-address mt-2 mb-3">
<a class="btn btn-outline-primary btn-sm" href="{$new_address_delivery_url}">{l s='add new address' d='Shop.Theme.Actions'}</a>
<a class="btn btn-primary btn-sm" href="{$new_address_delivery_url}">{l s='add new address' d='Shop.Theme.Actions'}</a>
</div>
{if $use_same_address && !$cart.is_virtual}
@@ -116,7 +116,7 @@
{/if}
<div class="add-address mt-2 mb-3">
<a class="btn btn-outline-primary btn-sm" href="{$new_address_invoice_url}">{l s='add new address' d='Shop.Theme.Actions'}</a>
<a class="btn btn-primary btn-sm" href="{$new_address_invoice_url}">{l s='add new address' d='Shop.Theme.Actions'}</a>
</div>
{/if}

View File

@@ -44,7 +44,7 @@
{block name='continue_shopping'}
<div class="my-3">
<a class="btn btn-outline-primary" href="{$urls.pages.index}">
<a class="btn btn-primary" href="{$urls.pages.index}">
<span class="material-icons btn-icon mr-1">keyboard_arrow_left</span>
{l s='Continue shopping' d='Shop.Theme.Actions'}
</a>

View File

@@ -12,9 +12,9 @@
{block name='modal_title' hide}
<h5 class="modal-title">{$smarty.block.child}</h5>
{/block}
{block name='modal_close'}
{block name='modal_close'}
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
{svg_icon file='x.svg' width="24" height="24"}
</button>
{/block}
</div>

View File

@@ -73,7 +73,7 @@
</div>
<div class="text-center customer-links__footer">
<a href="{$link->getPageLink('index', true, null, 'mylogout')}" class="customer-links__logout">
<a href="{$link->getPageLink('index', true, null, 'mylogout')}" class="customer-links__logout btn btn-secondary mt-2">
{l s='Sign out' d='Shop.Theme.Actions'}
</a>
</div>

View File

@@ -29,7 +29,11 @@
{/block}
{block name='page_content'}
<h6>{l s='Here are the orders you\'ve placed since your account was created.' d='Shop.Theme.Customeraccount'}</h6>
<p>
<strong>
{l s='Here are the orders you\'ve placed since your account was created.' d='Shop.Theme.Customeraccount'}
</strong>
</p>
{if $orders}
<table class="table table-striped table-bordered hidden-sm-down">
@@ -40,7 +44,7 @@
<th>{l s='Total price' d='Shop.Theme.Checkout'}</th>
<th class="hidden-md-down">{l s='Payment' d='Shop.Theme.Checkout'}</th>
<th class="hidden-md-down">{l s='Status' d='Shop.Theme.Checkout'}</th>
<th>&nbsp;</th>
<th>{l s='Actions' d='Shop.Theme.Checkout'}</th>
</tr>
</thead>
<tbody>
@@ -66,16 +70,14 @@
{$order.history.current.ostate_name}
</span>
</td>
<td class="text-sm-center order-actions align-middle">
<td class="order-actions align-middle d-flex">
<a class="view-order-details-link btn btn-sm btn-primary" href="{$order.details.details_url}" data-link-action="view-order-details">
{l s='Details' d='Shop.Theme.Customeraccount'}
</a>
{if $order.details.reorder_url}
<div class="col-sm-6 mt-2">
<a class="reorder-link btn btn-sm btn-primary" href="{$order.details.reorder_url}" data-link-action="view-order-details">
<a class="reorder-link btn btn-sm btn-secondary" href="{$order.details.reorder_url}" data-link-action="view-order-details">
{l s='Reorder' d='Shop.Theme.Actions'}
</a>
</div>
{/if}
</td>
</tr>

View File

@@ -66,7 +66,7 @@
{if $order.details.reorder_url}
<div class="mt-2 text-right">
<a href="{$order.details.reorder_url}" class="btn btn-outline-primary">{l s='Reorder' d='Shop.Theme.Actions'}</a>
<a href="{$order.details.reorder_url}" class="btn btn-primary">{l s='Reorder' d='Shop.Theme.Actions'}</a>
</div>
{/if}

View File

@@ -24,14 +24,8 @@
*}
{extends file='page.tpl'}
{block name='page_content_container'}
<section id="content" class="page-home">
{block name='page_content_top'}{/block}
{block name='page_content'}
{block name='hook_home'}
{$HOOK_HOME nofilter}
{/block}
{/block}
</section>
{/block}
{block name='page_content_container'}
<section id="content" class="page-home">
{* {include file='pages/index/hero.tpl'} *}
</section>
{/block}

View File

@@ -42,7 +42,7 @@
{include file='catalog/_partials/product-activation.tpl'}
{/block}
<header id="header" class="l-header">
<header id="header" class="l-header mb-0">
{block name='header'}
{include file='_partials/header.tpl'}
{/block}
@@ -55,12 +55,12 @@
{/block}
{hook h="displayWrapperTop"}
<div class="container">
<div class="{if $page.page_name == 'index' } container-fluid p-0 overflow-hidden {else}container{/if}">
{block name='breadcrumb'}
{include file='_partials/breadcrumb.tpl'}
{/block}
<div class="row">
<div {if $page.page_name != 'index' || !isset($page.body_classes['layout-full-width'])}class="row"{/if}>
{block name="left_column"}
<div id="left-column" class="col-12 col-md-4 col-lg-3">
{if $page.page_name == 'product'}
@@ -95,7 +95,7 @@
{hook h="displayWrapperBottom"}
</section>
<footer id="footer" class="l-footer js-footer">
<footer id="footer" class="l-footer js-footer mt-0">
{block name="footer"}
{include file="_partials/footer.tpl"}
{/block}

View File

@@ -28,7 +28,7 @@
{block name='right_column'}{/block}
{block name='content_wrapper'}
<div id="content-wrapper" class="col-12 js-content-wrapper">
<div id="content-wrapper" class="{if $page.page_name != 'index' || !isset($page.body_classes['layout-full-width'])}col-12 {/if}js-content-wrapper">
{hook h="displayContentWrapperTop"}
{block name='content'}
<p>Hello world! This is HTML5 Boilerplate.</p>

View File

@@ -0,0 +1,10 @@
{* hero section on home page *}
<section id="home_hero" class="section-spacer-both">
<div class="container">
<div class="row">
<div class="col-12 col-md-8 col-xl-6">
</div>
</div>
</div>
</section>