From dbee74a0df3e60779dd95536cf800cbcc1d589d8 Mon Sep 17 00:00:00 2001 From: Robin Morel Date: Mon, 29 Jun 2026 09:43:18 +0200 Subject: [PATCH 1/3] feat: update product card to add wishlist comp --- assets/css/wishlistAlert.css | 37 +++++ assets/css/wishlistModal.css | 131 ++++++++++++++++++ assets/icons/heart-fill.svg | 9 +- assets/icons/heart.svg | 7 +- base.html.twig | 81 ++++++----- .../Layout/PseSelector/PseSelector.html.twig | 10 +- src/UiComponents/ProductCard/ProductCard.css | 64 +++------ .../ProductCard/ProductCard.html.twig | 14 +- src/UiComponents/ProductCard/ProductCard.php | 22 ++- .../ProductCard/ProductCard.stories.js | 28 ++-- 10 files changed, 274 insertions(+), 129 deletions(-) create mode 100644 assets/css/wishlistAlert.css create mode 100644 assets/css/wishlistModal.css diff --git a/assets/css/wishlistAlert.css b/assets/css/wishlistAlert.css new file mode 100644 index 00000000..61f4d0e4 --- /dev/null +++ b/assets/css/wishlistAlert.css @@ -0,0 +1,37 @@ +.WishlistAlert { + border-radius: 8px; + padding: 48px 40px; + width: 100%; + max-width: 100%; + position: fixed; + bottom: 0; + left: 0; + z-index: 99; + background-color: var(--theme-lightest); + + @screen sm { + top: 30px; + right: 30px; + left: auto; + height: fit-content; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + box-shadow: 0px 0px 8px 0px #5454544d; + max-width: 518px; + padding: 24px 20px; + width: calc(100vw - 60px); + } + + &-iconClose { + position: absolute; + top: 10px; + right: 10px; + width: 35px; + cursor: pointer; + } + + &-title { + @apply mb-0 paragraph-2; + } +} diff --git a/assets/css/wishlistModal.css b/assets/css/wishlistModal.css new file mode 100644 index 00000000..21107587 --- /dev/null +++ b/assets/css/wishlistModal.css @@ -0,0 +1,131 @@ +.WishListButton { + display: inline-flex; +} + +.WishListModal { + z-index: 30; + + &-body { + display: flex; + flex-direction: column; + gap: 1.5rem; + max-height: 70vh; + overflow-y: auto; + } + + &-section { + display: flex; + flex-direction: column; + gap: 0.75rem; + } + + &-title { + @apply block mb-6 text-center text-black; + } + + &-titleSection { + @apply paragraph-2; + font-weight: 700; + color: var(--black); + } + + &-form { + display: flex; + flex-direction: column; + gap: 0.5rem; + } + + &-formRow { + display: flex; + gap: rem-convert(8px); + align-items: stretch; + + .FieldInput-blockInput { + flex: 1 1 auto; + } + } + + &-grid { + display: grid; + gap: 0.5rem; + + @screen md { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + } + + &-card { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 1.25rem; + border-radius: 12px; + background: var(--theme-lightest); + text-align: left; + cursor: pointer; + transition: border-color 0.15s ease; + + &:hover:not(.WishListModal--selected) { + background: var(--theme-lighter); + } + } + &-cardIcon { + width: rem-convert(18px); + height: rem-convert(18px); + flex: 0 0 auto; + } + + &-list { + display: flex; + flex-direction: column; + gap: rem-convert(8px); + margin: 0; + padding: 0; + list-style: none; + } + + &-item { + display: flex; + align-items: center; + justify-content: space-between; + gap: rem-convert(12px); + padding: rem-convert(10px) rem-convert(12px); + border: 1px solid var(--grey); + border-radius: rem-convert(12px); + } + + &-remove { + display: inline-flex; + align-items: center; + gap: rem-convert(6px); + flex: 0 0 auto; + margin-left: auto; + padding: 0.5rem 0.75rem; + + svg { + width: 1rem; + height: 1rem; + } + } +} + +@media (min-width: 1024px) { + .WishListModal.MobileDrawer { + top: 50%; + bottom: auto; + left: 50%; + right: auto; + width: min(560px, 92vw); + max-height: 85vh; + overflow-y: auto; + border-radius: rem-convert(20px); + } + + .WishListModal.MobileDrawer.is-open { + transform: translate(-50%, -50%); + } + + .WishListModal.MobileDrawer::before { + content: none; + } +} diff --git a/assets/icons/heart-fill.svg b/assets/icons/heart-fill.svg index 98e42b83..9884e7cd 100644 --- a/assets/icons/heart-fill.svg +++ b/assets/icons/heart-fill.svg @@ -1,5 +1,4 @@ - - - + + + \ No newline at end of file diff --git a/assets/icons/heart.svg b/assets/icons/heart.svg index 4793afde..c4d8fbfe 100644 --- a/assets/icons/heart.svg +++ b/assets/icons/heart.svg @@ -1,3 +1,4 @@ - - - + + + \ No newline at end of file diff --git a/base.html.twig b/base.html.twig index ab81b829..76b94532 100644 --- a/base.html.twig +++ b/base.html.twig @@ -2,34 +2,33 @@ - - - - - {% block title %} - {{ SEOnePageTitle() }} - {% endblock %} - - - {% block robots %}{% endblock %} - - - - - - {% block breadcrumb_json %} - {{ SEOneBreadcrumbJsonLd(breadcrumb)|raw }} - {% endblock %} - - - - - - {% block hreflang %} - {{SEOneHreflang()|raw}} - {% endblock %} - - + + + + + {% block title %} + {{ SEOnePageTitle() }} + {% endblock %} + + {% block robots %}{% endblock %} + + + + + + {% block breadcrumb_json %} + {{ SEOneBreadcrumbJsonLd(breadcrumb)|raw }} + {% endblock %} + + + + + + {% block hreflang %} + {{SEOneHreflang()|raw}} + {% endblock %} + + {% block stylesheets %} {{ encore_entry_link_tags('app') }} @@ -43,19 +42,19 @@ {% endblock %} - - {% block header %} - {{ include('@components/Layout/Header/Header.html.twig') }} - {% endblock %} + + {% block header %} + {{ include('@components/Layout/Header/Header.html.twig') }} + {% endblock %} -
- {% block body %}{% endblock %} -
+
{% block body %}{% endblock %} +
- {% block footer %} - {{ include('@components/Layout/Footer/Footer.html.twig') }} - {% endblock %} + {% block footer %} + {{ include('@components/Layout/Footer/Footer.html.twig') }} + {% endblock %} - {% block modals %}{% endblock %} - - + {% block modals %}{% endblock %} + {# {{ component('WishList:WishListAlert') }} #} + + diff --git a/components/Layout/PseSelector/PseSelector.html.twig b/components/Layout/PseSelector/PseSelector.html.twig index 60328667..66e6d5ef 100644 --- a/components/Layout/PseSelector/PseSelector.html.twig +++ b/components/Layout/PseSelector/PseSelector.html.twig @@ -4,7 +4,7 @@ 'data-action': 'live#action:prevent', 'data-live-action-param': 'save', }}, - ) }} + ) }} {% set outOfStock = this.isStockManaged() and currentPse.quantity == 0 %}
@@ -45,10 +45,10 @@ {% set available = true %}
  • {% endfor %} diff --git a/src/UiComponents/ProductCard/ProductCard.css b/src/UiComponents/ProductCard/ProductCard.css index e2370e43..6d506e73 100644 --- a/src/UiComponents/ProductCard/ProductCard.css +++ b/src/UiComponents/ProductCard/ProductCard.css @@ -6,54 +6,18 @@ background: var(--white); @apply rounded-lg; z-index: 0; - .ButtonWishList { - transition: 0.2s; - display: none; - - @screen sm { - display: block; - } - svg { - max-width: 35px; - } - &:focus-visible { - outline: none; - } - - .in-wishlist-icon { - display: none; - - &:hover { - svg { - color: var(--theme-medium); - } - } - } - - .not-in-wishlist-icon { - display: inline-block; - - &:hover { - svg { - color: var(--theme); - } - } - } - - &.in-wishlist { - .in-wishlist-icon { - display: inline-block; - } + &:focus, + &:focus-visible { + outline: none; - .not-in-wishlist-icon { - display: none; - } + .ProductCard-img { + outline: 2px solid theme('colors.gray.400'); } } &-header { - margin-bottom: 0.5rem; position: absolute; + margin-bottom: 0.5rem; display: flex; justify-content: space-between; width: 100%; @@ -229,4 +193,20 @@ outline: 2px solid theme('colors.gray.400'); } } + + &:has(.WishListModal) { + z-index: initial; + } +} + +.WishListButton { + width: 2.5rem; + height: 2.5rem; + padding: 0.5rem; + + > svg { + width: 100%; + height: 100%; + object-fit: contain; + } } diff --git a/src/UiComponents/ProductCard/ProductCard.html.twig b/src/UiComponents/ProductCard/ProductCard.html.twig index 3aa48ef6..dbd8ee17 100644 --- a/src/UiComponents/ProductCard/ProductCard.html.twig +++ b/src/UiComponents/ProductCard/ProductCard.html.twig @@ -1,8 +1,6 @@ {% props imgWidth = 300, imgHeight = 300, - wishButton = false, - inWishlist = false, reviewCount = 0 %} @@ -25,17 +23,7 @@ {{ include('@components/Molecules/Tag/Tag.html.twig', {customText: this.promoRate|format_number(style: 'percent'), variant: 'error'}) }} {% endif %}
    - - {% if wishButton %} - - {% endif %} + {{ component('WishList:WishListButton', {pseId: this.defaultPseId}) }}
    diff --git a/src/UiComponents/ProductCard/ProductCard.php b/src/UiComponents/ProductCard/ProductCard.php index 5e274c0a..110f1b4e 100644 --- a/src/UiComponents/ProductCard/ProductCard.php +++ b/src/UiComponents/ProductCard/ProductCard.php @@ -20,7 +20,6 @@ use Symfony\UX\TwigComponent\Attribute\PreMount; use Thelia\Api\Service\DataAccess\DataAccessService; use Thelia\Domain\Taxation\TaxEngine\TaxEngine; -use Thelia\Model\ProductPriceQuery; use Thelia\Model\ProductQuery; #[AsTwigComponent(name: 'Flexy:ProductCard', template: '@UiComponents/ProductCard/ProductCard.html.twig')] @@ -28,6 +27,7 @@ class ProductCard { public ?int $productId = null; private ?ProductDTO $product = null; + private ?int $defaultPseId = null; private ?int $imageId = null; private ?float $price = null; private ?float $taxedPrice = null; @@ -39,8 +39,7 @@ class ProductCard public function __construct( private readonly DataAccessService $dataAccessService, private TaxEngine $taxEngine, - ) - { + ) { } #[PreMount] @@ -55,7 +54,7 @@ public function mount(ProductDTO|array|null $product = null): void { if ($product instanceof ProductDTO) { $this->product = $product; - } else if (is_array($product)) { + } elseif (is_array($product)) { $this->product = ProductDTO::fromArray($product); } @@ -74,6 +73,8 @@ public function mount(ProductDTO|array|null $product = null): void $defaultPse = $this->findDefaultPse($this->product->productSaleElements); + $this->defaultPseId = $defaultPse?->id; + $this->setImage($defaultPse); if ($defaultPse instanceof ProductSaleElementDTO) { @@ -155,6 +156,17 @@ public function getProduct(): ProductDTO return $this->product; } + public function getDefaultPseId(): ?int + { + if (null !== $this->defaultPseId) { + return $this->defaultPseId; + } + + $firstPse = $this->product?->productSaleElements[0] ?? null; + + return $firstPse?->id; + } + public function getImageId(): ?int { return $this->imageId; @@ -183,7 +195,7 @@ private function setImage(?ProductSaleElementDTO $defaultPse): void $pseImages = $this->dataAccessService->resources( '/api/front/product_sale_elements_product_image', [ - 'productImageId' => array_map(static fn($img) => $img['id'], $productImages), + 'productImageId' => array_map(static fn ($img) => $img['id'], $productImages), 'productSaleElements.product.id' => $this->product->id, 'visible' => true, ] diff --git a/src/UiComponents/ProductCard/ProductCard.stories.js b/src/UiComponents/ProductCard/ProductCard.stories.js index 388d2d51..fa637131 100644 --- a/src/UiComponents/ProductCard/ProductCard.stories.js +++ b/src/UiComponents/ProductCard/ProductCard.stories.js @@ -6,22 +6,20 @@ export default { }; export const base = { - render : (args) => + render: (args) => `
    ${ProductCard(args)}
    `, - args : { - - img : { url: '/images/placeholder2.webp', alt: '' }, - url : '#', - title : 'Nom du produit', + args: { + img: { url: '/images/placeholder2.webp', alt: '' }, + url: '#', + title: 'Nom du produit', secondaryTitle: 'Titre secondaire', - quantity : 1, - attributesAv : { Taille: 'S' }, - price : '1000,00€', - promoPrice : '900,00€', - rate : 4, - reviewCount : 12, - wishButton : false, - colors : [ + quantity: 1, + attributesAv: { Taille: 'S' }, + price: '1000,00€', + promoPrice: '900,00€', + rate: 4, + reviewCount: 12, + colors: [ '#667761', '#84DCC6', '#C17767', @@ -31,7 +29,7 @@ export const base = { '#2E3438' ] }, - argTypes : {}, + argTypes: {}, parameters: { backgrounds: { default: 'theme-lighter' } } From 6d9c0299b577549ff88f198b497fabb52bc29709 Mon Sep 17 00:00:00 2001 From: Robin Morel Date: Mon, 29 Jun 2026 09:45:28 +0200 Subject: [PATCH 2/3] comment wishlist comp to prevent error --- src/UiComponents/ProductCard/ProductCard.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UiComponents/ProductCard/ProductCard.html.twig b/src/UiComponents/ProductCard/ProductCard.html.twig index dbd8ee17..e5721b79 100644 --- a/src/UiComponents/ProductCard/ProductCard.html.twig +++ b/src/UiComponents/ProductCard/ProductCard.html.twig @@ -23,7 +23,7 @@ {{ include('@components/Molecules/Tag/Tag.html.twig', {customText: this.promoRate|format_number(style: 'percent'), variant: 'error'}) }} {% endif %}
    - {{ component('WishList:WishListButton', {pseId: this.defaultPseId}) }} + {# {{ component('WishList:WishListButton', {pseId: this.defaultPseId}) }} #}
    From 6bfe7ab2ae28c475fc49878a0267a4f0528db293 Mon Sep 17 00:00:00 2001 From: Robin Morel Date: Mon, 29 Jun 2026 10:41:19 +0200 Subject: [PATCH 3/3] remove unused liveComp --- base.html.twig | 1 - index.html.twig | 2 -- src/UiComponents/ProductCard/ProductCard.html.twig | 1 - 3 files changed, 4 deletions(-) diff --git a/base.html.twig b/base.html.twig index 76b94532..bfdd5753 100644 --- a/base.html.twig +++ b/base.html.twig @@ -55,6 +55,5 @@ {% endblock %} {% block modals %}{% endblock %} - {# {{ component('WishList:WishListAlert') }} #} diff --git a/index.html.twig b/index.html.twig index c58e0871..4fa62a65 100644 --- a/index.html.twig +++ b/index.html.twig @@ -4,8 +4,6 @@ {% block body %} -{# {{ component('HeaderHighlights') }}#} - {{ component('Flexy:CrossSelling', {categoryId: 1, title: 'Last seen products'|trans}) }}
    diff --git a/src/UiComponents/ProductCard/ProductCard.html.twig b/src/UiComponents/ProductCard/ProductCard.html.twig index e5721b79..e076805d 100644 --- a/src/UiComponents/ProductCard/ProductCard.html.twig +++ b/src/UiComponents/ProductCard/ProductCard.html.twig @@ -23,7 +23,6 @@ {{ include('@components/Molecules/Tag/Tag.html.twig', {customText: this.promoRate|format_number(style: 'percent'), variant: 'error'}) }} {% endif %}
    - {# {{ component('WishList:WishListButton', {pseId: this.defaultPseId}) }} #}