```
✨ feat(shop): Ajoute la page de détails du produit avec schema.org.
```
This commit is contained in:
@@ -47,10 +47,19 @@ class ShopController extends AbstractController
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[Route(path: '/boutique/produit/{slug}', name: 'app_product_show', options: ['sitemap' => false], methods: ['GET'])]
|
#[Route(path: '/boutique/produit/{slug}', name: 'app_product_show', options: ['sitemap' => false], methods: ['GET'])]
|
||||||
public function indexProductShow(): Response
|
public function indexProductShow(?string $slug,ProductsRepository $productsRepository): Response
|
||||||
{
|
{
|
||||||
return $this->render('shop.twig', [
|
if(is_null($slug)) {
|
||||||
'featuredProducts' => []
|
return $this->redirectToRoute('app_shop');
|
||||||
|
}
|
||||||
|
$slug = explode('-', $slug);
|
||||||
|
$endId = end($slug);
|
||||||
|
if(!is_numeric($endId)) {
|
||||||
|
return $this->redirectToRoute('app_shop');
|
||||||
|
}
|
||||||
|
$p = $productsRepository->find($endId);
|
||||||
|
return $this->render('shop/product_details.twig', [
|
||||||
|
'product' => $p
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,17 +34,6 @@
|
|||||||
{{ 'events.list_main_title'|trans|default('Upcoming Events') }}
|
{{ 'events.list_main_title'|trans|default('Upcoming Events') }}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
{#
|
|
||||||
--- Events List Block ---
|
|
||||||
This section assumes an 'events' iterable variable is passed to the template.
|
|
||||||
Each item in 'events' is expected to be an object/array with:
|
|
||||||
- title (string)
|
|
||||||
- start_date (DateTime object)
|
|
||||||
- end_date (DateTime object)
|
|
||||||
- location (string)
|
|
||||||
- organizer (string)
|
|
||||||
- id (int/string for link)
|
|
||||||
#}
|
|
||||||
|
|
||||||
{% if events is defined and events is not empty %}
|
{% if events is defined and events is not empty %}
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 max-w-6xl mx-auto">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 max-w-6xl mx-auto">
|
||||||
|
|||||||
@@ -83,7 +83,7 @@
|
|||||||
},
|
},
|
||||||
"offers": {
|
"offers": {
|
||||||
"@type": "Offer",
|
"@type": "Offer",
|
||||||
"url": "{{ app.request.schemeAndHttpHost }}{{ path('app_product_show', {'slug': product.name|lower|replace({' ': '-'})}) }}",
|
"url": "{{ app.request.schemeAndHttpHost }}{{ path('app_product_show',{'slug': (product.name|lower|replace({' ': '-'}))~"-"~product.id}) }}",
|
||||||
"priceCurrency": "EUR",
|
"priceCurrency": "EUR",
|
||||||
"price": "{{ product.price }}",
|
"price": "{{ product.price }}",
|
||||||
"itemCondition": "https://schema.org/{% if product.state == 'new' %}NewCondition{% else %}UsedCondition{% endif %}",
|
"itemCondition": "https://schema.org/{% if product.state == 'new' %}NewCondition{% else %}UsedCondition{% endif %}",
|
||||||
@@ -167,7 +167,7 @@
|
|||||||
<span class="text-2xl font-extrabold text-indigo-600">
|
<span class="text-2xl font-extrabold text-indigo-600">
|
||||||
{{ product.price | number_format(2, ',', ' ') }} € TTC
|
{{ product.price | number_format(2, ',', ' ') }} € TTC
|
||||||
</span>
|
</span>
|
||||||
<a style="display: none" href="{{ path('app_product_show', {'slug': product.name|lower|replace({' ': '-'})}) }}" class="text-indigo-600 hover:text-indigo-800 text-sm font-semibold inline-flex items-center group">
|
<a href="{{ path('app_product_show', {'slug': (product.name|lower|replace({' ': '-'}))~"-"~product.id}) }}" class="text-indigo-600 hover:text-indigo-800 text-sm font-semibold inline-flex items-center group">
|
||||||
En savoir plus
|
En savoir plus
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-right ml-1 group-hover:translate-x-0.5 transition-transform"><path d="m9 18l6-6-6-6"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-right ml-1 group-hover:translate-x-0.5 transition-transform"><path d="m9 18l6-6-6-6"/></svg>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
201
templates/shop/product_details.twig
Normal file
201
templates/shop/product_details.twig
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
{% extends 'base.twig' %}
|
||||||
|
|
||||||
|
{# --- METADATA & SCHEMA --- #}
|
||||||
|
{% block title %}{{ product.name }}{% endblock %}
|
||||||
|
{% block meta_description %}{{ product.shortDescription }}{% endblock %}
|
||||||
|
|
||||||
|
{% block canonical_url %}<link rel="canonical" href="{{ app.request.pathInfo }}" />{% endblock %}
|
||||||
|
{% block breadcrumb_schema %}
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "BreadcrumbList",
|
||||||
|
"itemListElement": [
|
||||||
|
{
|
||||||
|
"@type": "ListItem",
|
||||||
|
"position": 1,
|
||||||
|
"name": "{{ 'breadcrumb.home'|trans }}",
|
||||||
|
"item": "{{ app.request.schemeAndHttpHost }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "ListItem",
|
||||||
|
"position": 2,
|
||||||
|
"name": "{{ product.name }}",
|
||||||
|
"item": "{{ app.request.schemeAndHttpHost }}{{ app.request.pathInfo }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org/",
|
||||||
|
"@type": "ImageObject",
|
||||||
|
"contentUrl": "{{ vich_uploader_asset(product,'image') | imagine_filter('webp') }}",
|
||||||
|
"license": "https://example.com/license",
|
||||||
|
"acquireLicensePage": "{{ app.request.schemeAndHttpHost}}{{ path('app_legal') }}",
|
||||||
|
"creditText": "E-Cosplay",
|
||||||
|
"creator": {
|
||||||
|
"@type": "Person",
|
||||||
|
"name": "E-Cosplay"
|
||||||
|
},
|
||||||
|
"copyrightNotice": "E-Cosplay"
|
||||||
|
}}
|
||||||
|
</script>
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org/",
|
||||||
|
"@type": "Product",
|
||||||
|
"name": "{{ product.name }}",
|
||||||
|
"image": "{{ vich_uploader_asset(product,'image') | imagine_filter('webp') }}",
|
||||||
|
"description": "{{ product.shortDescription }}",
|
||||||
|
"sku": "EC-{{ product.id }}",
|
||||||
|
"brand": {
|
||||||
|
"@type": "Brand",
|
||||||
|
"name": "E-COSPLAY"
|
||||||
|
},
|
||||||
|
"shippingDetails": {
|
||||||
|
"@type": "OfferShippingDetails",
|
||||||
|
"shippingRate": {
|
||||||
|
"@type": "MonetaryAmount",
|
||||||
|
"value": 6.00,
|
||||||
|
"currency": "EUR"
|
||||||
|
},
|
||||||
|
"shippingDestination": {
|
||||||
|
"@type": "DefinedRegion",
|
||||||
|
"addressCountry": "FR"
|
||||||
|
},
|
||||||
|
"deliveryTime": {
|
||||||
|
"@type": "ShippingDeliveryTime",
|
||||||
|
"handlingTime": {
|
||||||
|
"@type": "QuantitativeValue",
|
||||||
|
"minValue": 0,
|
||||||
|
"maxValue": 1,
|
||||||
|
"unitCode": "DAY"
|
||||||
|
},
|
||||||
|
"transitTime": {
|
||||||
|
"@type": "QuantitativeValue",
|
||||||
|
"minValue": 1,
|
||||||
|
"maxValue": 5,
|
||||||
|
"unitCode": "DAY"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"offers": {
|
||||||
|
"@type": "Offer",
|
||||||
|
"url": "{{ app.request.schemeAndHttpHost }}{{ path('app_product_show', {'slug': (product.name|lower|replace({' ': '-'}))~"-"~product.id}) }}",
|
||||||
|
"priceCurrency": "EUR",
|
||||||
|
"price": "{{ product.price }}",
|
||||||
|
"itemCondition": "https://schema.org/{% if product.state == 'new' %}NewCondition{% else %}UsedCondition{% endif %}",
|
||||||
|
"availability": "https://schema.org/InStock",
|
||||||
|
"hasMerchantReturnPolicy": {
|
||||||
|
"@type": "MerchantReturnPolicy",
|
||||||
|
"applicableCountry": "FR",
|
||||||
|
"returnPolicyCategory": "https://schema.org/{% if product.custom %}MerchantReturnNotPermitted {% else %}MerchantReturnFiniteReturnWindow{% endif %}",
|
||||||
|
"merchantReturnDays": {% if product.custom %}0{%else%}14{% endif %},
|
||||||
|
"returnFees": "https://schema.org/{% if product.custom %}ReturnFeesCustomerResponsibility{%else%}ReturnFeesCustomerResponsibility{% endif %}",
|
||||||
|
"returnMethod": "https://schema.org/ReturnByMail"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{# --- BODY --- #}
|
||||||
|
{% block body %}
|
||||||
|
{# Ajout d'une balise style pour définir les couleurs personnalisées du bouton #}
|
||||||
|
<style>
|
||||||
|
.btn-custom-yellow {
|
||||||
|
background-color: #FABF04; /* Couleur de base: Jaune vif */
|
||||||
|
color: #1f2937; /* Texte gris foncé pour un meilleur contraste */
|
||||||
|
}
|
||||||
|
.btn-custom-yellow:hover {
|
||||||
|
background-color: #e6a800; /* Jaune légèrement plus foncé au survol */
|
||||||
|
}
|
||||||
|
.btn-custom-yellow:focus {
|
||||||
|
--tw-ring-color: #f7d976; /* Anneau de focus jaune clair */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<main class="container mx-auto px-4 py-8">
|
||||||
|
{# Conteneur simplifié et épuré #}
|
||||||
|
<div class="max-w-7xl mx-auto p-4 lg:p-6">
|
||||||
|
{# Titre H1 #}
|
||||||
|
<h1 class="text-3xl lg:text-4xl font-extrabold text-gray-900 mb-6 text-center lg:text-left">
|
||||||
|
{{ product.name }}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div class="lg:grid lg:grid-cols-2 lg:gap-12">
|
||||||
|
{# --- Product Image (Left Column on Desktop) --- #}
|
||||||
|
<div class="mb-8 lg:mb-0">
|
||||||
|
<img src="{{ vich_uploader_asset(product,'image') | imagine_filter('webp') }}"
|
||||||
|
alt="{{ product.name }}"
|
||||||
|
class="w-full h-auto rounded-lg transition transform hover:scale-[1.01] duration-300"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# --- Product Details & Actions (Right Column on Desktop) --- #}
|
||||||
|
<div class="flex flex-col space-y-8">
|
||||||
|
{# --- Price (Simple TTC Display) --- #}
|
||||||
|
<div class="pt-2">
|
||||||
|
<p class="text-5xl font-extrabold text-red-600">
|
||||||
|
{{ product.price|number_format(2, ',', ' ') }} € TTC
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# --- Short Description --- #}
|
||||||
|
<div class="pt-1">
|
||||||
|
<p class="text-xl text-gray-700 leading-relaxed">
|
||||||
|
{{ product.shortDescription }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# --- Reference and Custom/Handmade Tags --- #}
|
||||||
|
<div class="space-y-4 pt-2">
|
||||||
|
{# Product Reference #}
|
||||||
|
<p class="text-base text-gray-600">
|
||||||
|
<strong>{{ 'product_ref'|trans }}:</strong> <span class="font-medium text-gray-800">{{ product.ref }}</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{# Custom / Fait Main Badges #}
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
{% if product.handmade %}
|
||||||
|
<span class="inline-flex items-center px-3 py-1 text-sm font-semibold bg-pink-100 text-pink-800 rounded-full shadow-md">
|
||||||
|
<i class="fas fa-hand-sparkles mr-2"></i> {{ 'product_handmade'|trans }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if product.custom %}
|
||||||
|
<span class="inline-flex items-center px-3 py-1 text-sm font-semibold bg-indigo-100 text-indigo-800 rounded-full shadow-md">
|
||||||
|
<i class="fas fa-crown mr-2"></i> {{ 'product_custom'|trans }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# --- Action: Add to Cart Button (Updated with custom yellow class) --- #}
|
||||||
|
<div class="pt-4" style="display:none;">
|
||||||
|
<button type="button"
|
||||||
|
class="w-full lg:w-3/4 px-6 py-4 btn-custom-yellow font-bold text-lg rounded-xl shadow-lg transition duration-300 transform hover:scale-[1.01] focus:outline-none focus:ring-4 focus:ring-opacity-75">
|
||||||
|
<i class="fas fa-shopping-cart mr-2"></i> {{ 'product_add_to_cart'|trans }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Messages de livraison/stock traduits #}
|
||||||
|
<div class="text-sm text-gray-500 pt-2 space-y-1">
|
||||||
|
<p>{{ 'product_estimated_delivery'|trans }}</p>
|
||||||
|
<p>{{ 'product_shipping_methods'|trans }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# --- Long Description (Full Width Below) --- #}
|
||||||
|
<div class="mt-12 pt-8 border-t border-gray-300">
|
||||||
|
<h2 class="text-2xl font-bold text-gray-800 mb-4">{{'product_long_desc_title'|trans}}</h2>
|
||||||
|
<div class="prose max-w-none text-gray-700 leading-relaxed">
|
||||||
|
{{ product.longDescription|raw }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{% endblock %}
|
||||||
@@ -663,3 +663,18 @@ event_website_launch_date: "November 15, 2025"
|
|||||||
event_website_launch_title: "Website Launch"
|
event_website_launch_title: "Website Launch"
|
||||||
event_website_launch_text: "Launch of our official platform to inform about our activities, manage registrations, and share our creations."
|
event_website_launch_text: "Launch of our official platform to inform about our activities, manage registrations, and share our creations."
|
||||||
timeline_title: "Our Timeline"
|
timeline_title: "Our Timeline"
|
||||||
|
# --- New Product Page Keys ---
|
||||||
|
product_add_to_cart: "Add to Cart"
|
||||||
|
product_ref: "Reference"
|
||||||
|
product_state: "Condition"
|
||||||
|
product_state_new: "New"
|
||||||
|
product_state_used: "Used"
|
||||||
|
product_features_title: "Features"
|
||||||
|
product_handmade: "Handmade (Artisanal)"
|
||||||
|
product_not_handmade: "Industrial/Manufactured"
|
||||||
|
product_custom: "Unique Piece (Custom)"
|
||||||
|
product_not_custom: "Standard Item"
|
||||||
|
product_short_desc_title: "Quick Overview"
|
||||||
|
product_long_desc_title: "Detailed Description"
|
||||||
|
product_estimated_delivery: "Estimated delivery: 2-5 working days."
|
||||||
|
product_shipping_methods: "Shipping via Mondial Relay / Colissimo starting from €6 incl. tax"
|
||||||
|
|||||||
@@ -597,3 +597,17 @@ event_miss_tergnier_text: "Signature du partenariat avec Miss Tergnier 2025."
|
|||||||
event_website_launch_date: "15 novembre, 2025"
|
event_website_launch_date: "15 novembre, 2025"
|
||||||
event_website_launch_title: "Lancement du Site Internet"
|
event_website_launch_title: "Lancement du Site Internet"
|
||||||
event_website_launch_text: "Mise en ligne de notre plateforme officielle pour informer sur nos activités, gérer les inscriptions et partager nos créations."
|
event_website_launch_text: "Mise en ligne de notre plateforme officielle pour informer sur nos activités, gérer les inscriptions et partager nos créations."
|
||||||
|
product_add_to_cart: "Ajouter au panier"
|
||||||
|
product_ref: "Référence"
|
||||||
|
product_state: "État"
|
||||||
|
product_state_new: "Neuf"
|
||||||
|
product_state_used: "Occasion"
|
||||||
|
product_features_title: "Caractéristiques"
|
||||||
|
product_handmade: "Fait Main (Artisanal)"
|
||||||
|
product_not_handmade: "Industriel/Manufacturé"
|
||||||
|
product_custom: "Pièce Unique (Sur Mesure)"
|
||||||
|
product_not_custom: "Article Standard"
|
||||||
|
product_short_desc_title: "Aperçu rapide"
|
||||||
|
product_long_desc_title: "Description détaillée"
|
||||||
|
product_estimated_delivery: "Livraison estimée : 2-5 jours ouvrés."
|
||||||
|
product_shipping_methods: "Livraison par Mondial Relay / Colissimo à partir de 6€ TTC"
|
||||||
|
|||||||
Reference in New Issue
Block a user