@salifbiaye/create-boutique 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/dist/code.js +110 -0
- package/cli/dist/commands.js +143 -10
- package/cli/dist/create.js +54 -7
- package/cli/dist/generate.js +65 -4
- package/cli/dist/guide.js +10 -2
- package/cli/dist/index.js +20 -7
- package/cli/dist/payments.js +150 -0
- package/docs/fiche-client.en.html +2 -2
- package/docs/fiche-client.html +2 -2
- package/docs/guide-complet.fr.html +681 -256
- package/docs/guide-images/categories.jpg +0 -0
- package/docs/guide-images/clients.jpg +0 -0
- package/docs/guide-images/collections.jpg +0 -0
- package/docs/guide-images/commande-detail.jpg +0 -0
- package/docs/guide-images/commande-expedier.jpg +0 -0
- package/docs/guide-images/commande-menu.jpg +0 -0
- package/docs/guide-images/commande-paiement.jpg +0 -0
- package/docs/guide-images/commandes-liste.jpg +0 -0
- package/docs/guide-images/connexion.jpg +0 -0
- package/docs/guide-images/groupes-clients.jpg +0 -0
- package/docs/guide-images/inventaire.jpg +0 -0
- package/docs/guide-images/listes-prix.jpg +0 -0
- package/docs/guide-images/menu.jpg +0 -0
- package/docs/guide-images/mes-boutiques-fermer.jpg +0 -0
- package/docs/guide-images/mes-boutiques-panneau.jpg +0 -0
- package/docs/guide-images/mes-boutiques.jpg +0 -0
- package/docs/guide-images/parametres-boutique.jpg +0 -0
- package/docs/guide-images/parametres-canaux.jpg +0 -0
- package/docs/guide-images/parametres-emplacements.jpg +0 -0
- package/docs/guide-images/parametres-profil.jpg +0 -0
- package/docs/guide-images/parametres-regions.jpg +0 -0
- package/docs/guide-images/parametres-utilisateurs.jpg +0 -0
- package/docs/guide-images/produit-creer-details.jpg +0 -0
- package/docs/guide-images/produit-detail.jpg +0 -0
- package/docs/guide-images/produit-variantes-menu.jpg +0 -0
- package/docs/guide-images/produit-variantes.jpg +0 -0
- package/docs/guide-images/produits-liste.jpg +0 -0
- package/docs/guide-images/promotion-details.jpg +0 -0
- package/docs/guide-images/promotion-type.jpg +0 -0
- package/docs/guide-images/promotions-liste.jpg +0 -0
- package/docs/guide-images/site-boutique.jpg +0 -0
- package/docs/guide-images/site-fermee.png +0 -0
- package/docs/guide-images/vue-ensemble.jpg +0 -0
- package/package.json +3 -2
- package/templates/backend/medusa-config.ts +31 -25
- package/templates/backend/package-lock.json +21 -0
- package/templates/backend/package.json +2 -0
- package/templates/backend/src/admin/i18n/json/en.json +51 -3
- package/templates/backend/src/admin/i18n/json/fr.json +51 -3
- package/templates/backend/src/admin/routes/boutique-campaigns/campaign-drawer.tsx +2 -0
- package/templates/backend/src/admin/routes/boutique-campaigns/page.tsx +36 -1
- package/templates/backend/src/admin/routes/reviews/page.tsx +259 -0
- package/templates/backend/src/api/admin/boutique-campaigns/route.ts +3 -1
- package/templates/backend/src/api/admin/boutique-online/[salesChannelId]/route.ts +22 -0
- package/templates/backend/src/api/admin/reviews/[id]/route.ts +15 -0
- package/templates/backend/src/api/admin/reviews/route.ts +47 -0
- package/templates/backend/src/api/middlewares.ts +7 -0
- package/templates/backend/src/api/store/campaign/route.ts +5 -1
- package/templates/backend/src/api/store/reviews/latest/route.ts +39 -0
- package/templates/backend/src/api/store/reviews/order/route.ts +36 -0
- package/templates/backend/src/api/store/reviews/route.ts +43 -0
- package/templates/backend/src/api/store/reviews/summary/route.ts +28 -0
- package/templates/backend/src/emails/en.json +3 -1
- package/templates/backend/src/emails/fr.json +3 -1
- package/templates/backend/src/lib/email-config.ts +14 -0
- package/templates/backend/src/lib/emails.ts +9 -0
- package/templates/backend/src/lib/notify.ts +14 -8
- package/templates/backend/src/lib/payment-config.ts +66 -0
- package/templates/backend/src/lib/project.ts +12 -0
- package/templates/backend/src/lib/redirect-payment.ts +264 -0
- package/templates/backend/src/lib/review-token.ts +40 -0
- package/templates/backend/src/lib/reviews.ts +25 -0
- package/templates/backend/src/modules/naboopay/index.ts +6 -0
- package/templates/backend/src/modules/naboopay/service.ts +92 -0
- package/templates/backend/src/modules/paydunya/index.ts +6 -0
- package/templates/backend/src/modules/paydunya/service.ts +89 -0
- package/templates/backend/src/modules/paytech/index.ts +6 -0
- package/templates/backend/src/modules/paytech/service.ts +114 -0
- package/templates/backend/src/modules/product-review/index.ts +8 -0
- package/templates/backend/src/modules/product-review/migrations/Migration20261001090000.ts +18 -0
- package/templates/backend/src/modules/product-review/models/review.ts +25 -0
- package/templates/backend/src/modules/product-review/service.ts +6 -0
- package/templates/backend/src/modules/product-review/validators.ts +27 -0
- package/templates/backend/src/modules/smtp/index.ts +6 -0
- package/templates/backend/src/modules/smtp/service.ts +73 -0
- package/templates/backend/src/scripts/seed.ts +52 -36
- package/templates/backend/src/subscribers/fulfillment-emails.ts +1 -1
- package/templates/backend/src/workflows/reviews.ts +21 -0
- package/templates/backend/src/workflows/steps/create-review.ts +52 -0
- package/templates/backend/src/workflows/steps/update-review.ts +36 -0
- package/templates/storefront/PERSONNALISER.md +134 -0
- package/templates/storefront/locales/en.json +198 -11
- package/templates/storefront/locales/fr.json +198 -11
- package/templates/storefront/public/custom/LISEZMOI.txt +2 -0
- package/templates/storefront/src/app/(checkout)/layout.tsx +1 -1
- package/templates/storefront/src/app/(main)/layout.tsx +8 -1
- package/templates/storefront/src/app/(main)/legal/[page]/page.tsx +42 -7
- package/templates/storefront/src/app/(main)/page.tsx +149 -4
- package/templates/storefront/src/app/(main)/review/page.tsx +60 -0
- package/templates/storefront/src/app/(main)/store/page.tsx +3 -1
- package/templates/storefront/src/app/(main)/template.tsx +17 -2
- package/templates/storefront/src/app/layout.tsx +13 -1
- package/templates/storefront/src/lib/boutique/campaign.ts +5 -1
- package/templates/storefront/src/lib/boutique/page-name.ts +25 -0
- package/templates/storefront/src/lib/boutique/schema.ts +3 -1
- package/templates/storefront/src/lib/constants.tsx +17 -0
- package/templates/storefront/src/lib/data/review-actions.ts +28 -0
- package/templates/storefront/src/lib/data/reviews.ts +64 -0
- package/templates/storefront/src/modules/checkout/components/payment/index.tsx +26 -4
- package/templates/storefront/src/modules/checkout/components/payment-button/index.tsx +85 -2
- package/templates/storefront/src/modules/products/templates/index.tsx +5 -0
- package/templates/storefront/src/modules/store/components/shop-search.tsx +85 -0
- package/templates/storefront/src/modules/store/templates/index.tsx +16 -8
- package/templates/storefront/src/modules/store/templates/paginated-products.tsx +15 -3
- package/templates/storefront/src/modules/ub/album.tsx +2 -0
- package/templates/storefront/src/modules/ub/arena.tsx +182 -0
- package/templates/storefront/src/modules/ub/campaign-blocks.tsx +3 -2
- package/templates/storefront/src/modules/ub/campaign-ended.tsx +27 -13
- package/templates/storefront/src/modules/ub/cinema.tsx +2 -0
- package/templates/storefront/src/modules/ub/cookie-banner.tsx +62 -0
- package/templates/storefront/src/modules/ub/gala.tsx +2 -0
- package/templates/storefront/src/modules/ub/grimoire.tsx +35 -8
- package/templates/storefront/src/modules/ub/harvest.tsx +2 -1
- package/templates/storefront/src/modules/ub/hero-image.tsx +14 -0
- package/templates/storefront/src/modules/ub/home-kit.ts +45 -0
- package/templates/storefront/src/modules/ub/lantern.tsx +244 -0
- package/templates/storefront/src/modules/ub/locker.tsx +180 -0
- package/templates/storefront/src/modules/ub/mosaic.tsx +189 -0
- package/templates/storefront/src/modules/ub/nav.tsx +8 -5
- package/templates/storefront/src/modules/ub/review-form.tsx +62 -0
- package/templates/storefront/src/modules/ub/reviews.tsx +131 -0
- package/templates/storefront/src/modules/ub/riviera.tsx +175 -0
- package/templates/storefront/src/modules/ub/souk.tsx +214 -0
- package/templates/storefront/src/modules/ub/sunset-checklist.tsx +95 -0
- package/templates/storefront/src/modules/ub/sunset.tsx +164 -0
- package/templates/storefront/src/modules/ub/tech.tsx +2 -2
- package/templates/storefront/src/modules/ub/training.tsx +196 -0
- package/templates/storefront/src/proxy.ts +2 -0
- package/templates/storefront/src/styles/custom.css +26 -0
- package/templates/storefront/src/styles/ub/album.css +1 -1
- package/templates/storefront/src/styles/ub/arena.css +120 -0
- package/templates/storefront/src/styles/ub/cinema.css +1 -1
- package/templates/storefront/src/styles/ub/gala.css +1 -1
- package/templates/storefront/src/styles/ub/grimoire.css +23 -7
- package/templates/storefront/src/styles/ub/harvest.css +7 -6
- package/templates/storefront/src/styles/ub/lantern.css +157 -0
- package/templates/storefront/src/styles/ub/locker.css +115 -0
- package/templates/storefront/src/styles/ub/media.css +10 -0
- package/templates/storefront/src/styles/ub/mosaic.css +118 -0
- package/templates/storefront/src/styles/ub/motion.css +0 -3
- package/templates/storefront/src/styles/ub/overrides.css +10 -0
- package/templates/storefront/src/styles/ub/pages.css +176 -0
- package/templates/storefront/src/styles/ub/reviews.css +85 -0
- package/templates/storefront/src/styles/ub/riviera.css +103 -0
- package/templates/storefront/src/styles/ub/souk.css +144 -0
- package/templates/storefront/src/styles/ub/sunset.css +123 -0
- package/templates/storefront/src/styles/ub/tech.css +2 -5
- package/templates/storefront/src/styles/ub/training.css +120 -0
- package/templates/storefront/themes/jeux/campaign.en.json +31 -0
- package/templates/storefront/themes/jeux/campaign.fr.json +31 -0
- package/templates/storefront/themes/jeux/theme.json +11 -0
- package/templates/storefront/themes/jeux-2/campaign.en.json +31 -0
- package/templates/storefront/themes/jeux-2/campaign.fr.json +31 -0
- package/templates/storefront/themes/jeux-2/theme.json +11 -0
- package/templates/storefront/themes/mubarak/campaign.en.json +31 -0
- package/templates/storefront/themes/mubarak/campaign.fr.json +31 -0
- package/templates/storefront/themes/mubarak/theme.json +11 -0
- package/templates/storefront/themes/mubarak-2/campaign.en.json +31 -0
- package/templates/storefront/themes/mubarak-2/campaign.fr.json +31 -0
- package/templates/storefront/themes/mubarak-2/theme.json +11 -0
- package/templates/storefront/themes/sport/campaign.en.json +31 -0
- package/templates/storefront/themes/sport/campaign.fr.json +31 -0
- package/templates/storefront/themes/sport/theme.json +11 -0
- package/templates/storefront/themes/sport-2/campaign.en.json +31 -0
- package/templates/storefront/themes/sport-2/campaign.fr.json +31 -0
- package/templates/storefront/themes/sport-2/theme.json +11 -0
- package/templates/storefront/themes/summer/campaign.en.json +31 -0
- package/templates/storefront/themes/summer/campaign.fr.json +31 -0
- package/templates/storefront/themes/summer/theme.json +11 -0
- package/templates/storefront/themes/summer-2/campaign.en.json +31 -0
- package/templates/storefront/themes/summer-2/campaign.fr.json +31 -0
- package/templates/storefront/themes/summer-2/theme.json +11 -0
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"hero.trust.shipping": "Livraison offerte",
|
|
14
14
|
"hero.trust.shippingSub": "Sur les commandes éligibles",
|
|
15
15
|
"hero.trust.secure": "Paiement sécurisé",
|
|
16
|
-
"hero.trust.secureSub": "
|
|
16
|
+
"hero.trust.secureSub": "Paiement chiffré",
|
|
17
17
|
"hero.trust.returns": "Retours faciles",
|
|
18
18
|
"hero.trust.returnsSub": "Sans justification",
|
|
19
19
|
"countdown.label": "L'offre se termine dans",
|
|
@@ -52,12 +52,12 @@
|
|
|
52
52
|
"pdp.trust.warranty": "Garantie 2 ans",
|
|
53
53
|
"pdp.faq": "Questions fréquentes",
|
|
54
54
|
"pdp.faq.q1": "Quels moyens de paiement acceptez-vous ?",
|
|
55
|
-
"pdp.faq.a1": "Carte
|
|
55
|
+
"pdp.faq.a1": "Carte, Wave, Orange Money et les autres moyens affichés au paiement, traités par nos partenaires de paiement sécurisés.",
|
|
56
56
|
"pdp.faq.q2": "Quel est le délai de livraison ?",
|
|
57
57
|
"pdp.faq.a2": "Les modes et délais de livraison sont affichés au moment de la commande.",
|
|
58
58
|
"pdp.faq.q3": "Puis-je retourner ma commande ?",
|
|
59
59
|
"pdp.faq.a3": "Oui, vous disposez de 30 jours pour retourner un article dans son état d'origine.",
|
|
60
|
-
"footer.tagline": "Des produits choisis, expédiés avec soin. Paiement
|
|
60
|
+
"footer.tagline": "Des produits choisis, expédiés avec soin. Paiement 100 % sécurisé.",
|
|
61
61
|
"footer.shop": "Boutique",
|
|
62
62
|
"footer.help": "Aide",
|
|
63
63
|
"footer.company": "Informations",
|
|
@@ -72,14 +72,10 @@
|
|
|
72
72
|
"legal.contact": "Contact",
|
|
73
73
|
"legal.shipping.body": "Les commandes sont préparées sous 1 à 2 jours ouvrés. Les modes de livraison, leurs tarifs et les délais estimés sont affichés avant le paiement.",
|
|
74
74
|
"legal.returns.body": "Vous pouvez retourner un article sous 30 jours après réception, non utilisé et dans son emballage d'origine. Contactez-nous pour lancer un retour ; le remboursement se fait sur le moyen de paiement d'origine.",
|
|
75
|
-
"legal.privacy.body": "{storeName} ne collecte que les données nécessaires à votre commande (nom, adresse, e-mail, téléphone). Les
|
|
75
|
+
"legal.privacy.body": "{storeName} ne collecte que les données nécessaires à votre commande (nom, adresse, e-mail, téléphone). Les paiements sont traités par nos partenaires de paiement sécurisés (carte, Wave, Orange Money…) : nous ne voyons ni ne conservons jamais vos données de paiement. Cookies : uniquement ceux nécessaires au site (panier, connexion) ; aucun cookie publicitaire ni de suivi. Contactez-nous pour consulter ou supprimer vos données.",
|
|
76
76
|
"legal.terms.body": "Les prix incluent les remises affichées au moment de la commande. Le montant débité est celui affiché au paiement. {storeName} peut annuler une commande en cas d'erreur de prix manifeste ; vous êtes alors intégralement remboursé.",
|
|
77
77
|
"legal.contact.body": "Une question sur une commande ? Écrivez-nous, nous vous répondrons au plus vite.",
|
|
78
78
|
"legal.contact.email": "E-mail : {email}",
|
|
79
|
-
"ended.title": "Cette vente est terminée",
|
|
80
|
-
"ended.text": "Merci d'avoir fait vos achats chez {storeName}. Notre prochaine vente vous attend déjà.",
|
|
81
|
-
"ended.cta": "Voir la prochaine vente",
|
|
82
|
-
"ended.soon": "Notre prochaine vente arrive bientôt.",
|
|
83
79
|
"upcoming.title": "Bientôt disponible",
|
|
84
80
|
"upcoming.text": "Cette vente commence le {date}.",
|
|
85
81
|
"notFound.title": "Page introuvable",
|
|
@@ -89,7 +85,7 @@
|
|
|
89
85
|
"track.text": "Connectez-vous à votre compte pour suivre vos commandes, ou consultez l'e-mail de confirmation que nous vous avons envoyé.",
|
|
90
86
|
"track.cta": "Accéder à mon compte",
|
|
91
87
|
"checkout.back": "Retour au panier",
|
|
92
|
-
"checkout.secure": "Les paiements sont chiffrés et traités par
|
|
88
|
+
"checkout.secure": "Les paiements sont chiffrés et traités par nos partenaires de paiement sécurisés.",
|
|
93
89
|
"form.select": "Choisir…",
|
|
94
90
|
"form.firstName": "Prénom",
|
|
95
91
|
"form.lastName": "Nom",
|
|
@@ -292,7 +288,7 @@
|
|
|
292
288
|
"contact.faq.q2": "Comment retourner un article ?",
|
|
293
289
|
"contact.faq.a2": "Écrivez-nous dans les 30 jours avec votre numéro de commande : nous vous envoyons les instructions de retour. Le remboursement est fait dès réception.",
|
|
294
290
|
"contact.faq.q3": "Le paiement est-il sécurisé ?",
|
|
295
|
-
"contact.faq.a3": "Oui. Les paiements sont traités par
|
|
291
|
+
"contact.faq.a3": "Oui. Les paiements sont traités par des partenaires de paiement sécurisés (carte, Wave, Orange Money…) : {storeName} ne voit ni ne stocke jamais vos données de paiement.",
|
|
296
292
|
"contact.faq.q4": "Puis-je modifier ou annuler ma commande ?",
|
|
297
293
|
"contact.faq.a4": "Tant qu'elle n'est pas expédiée, oui : contactez-nous au plus vite avec votre numéro de commande.",
|
|
298
294
|
"section.fold": "Les offres phares",
|
|
@@ -331,5 +327,196 @@
|
|
|
331
327
|
"grimoire.draw": "Tirez une carte",
|
|
332
328
|
"grimoire.drawSub": "Trois cartes, trois offres ensorcelées. Survolez une carte pour la retourner.",
|
|
333
329
|
"grimoire.reveal": "Retourner",
|
|
334
|
-
"grimoire.shelf": "L'étagère de l'apothicaire"
|
|
330
|
+
"grimoire.shelf": "L'étagère de l'apothicaire",
|
|
331
|
+
"closed.title": "Boutique fermée pour le moment",
|
|
332
|
+
"closed.text": "Merci de votre visite chez {storeName}. La boutique rouvrira bientôt.",
|
|
333
|
+
"closed.cta": "Voir notre autre boutique",
|
|
334
|
+
"closed.track": "Suivre ma commande",
|
|
335
|
+
"riviera.looksKicker": "Le lookbook",
|
|
336
|
+
"riviera.look": "Look N°{n}",
|
|
337
|
+
"riviera.coloursKicker": "Par couleur",
|
|
338
|
+
"riviera.colours": "Les couleurs de l'été",
|
|
339
|
+
"riviera.all": "Toute la collection",
|
|
340
|
+
"riviera.step1": "Choisissez",
|
|
341
|
+
"riviera.step1Sub": "Une pièce, une couleur, votre taille.",
|
|
342
|
+
"riviera.step2": "Recevez",
|
|
343
|
+
"riviera.step2Sub": "Expédiée soigneusement, suivie jusqu'à chez vous.",
|
|
344
|
+
"riviera.step3": "Profitez",
|
|
345
|
+
"riviera.step3Sub": "Retours gratuits sous 30 jours.",
|
|
346
|
+
"sunset.postcardsKicker": "Cartes postales",
|
|
347
|
+
"sunset.checklistKicker": "Avant de partir",
|
|
348
|
+
"sunset.checklist": "La liste de la plage",
|
|
349
|
+
"sunset.checklistSub": "Cochez ce qu'il vous manque, on additionne pour vous.",
|
|
350
|
+
"sunset.total": "Total",
|
|
351
|
+
"sunset.addAll": "Tout ajouter au panier",
|
|
352
|
+
"sunset.added": "Ajouté au panier",
|
|
353
|
+
"sunset.choose": "choisir la taille",
|
|
354
|
+
"sunset.empty": "Cochez au moins un article",
|
|
355
|
+
"locker.home": "Domicile",
|
|
356
|
+
"locker.away": "Extérieur",
|
|
357
|
+
"locker.clock": "Fin du match dans",
|
|
358
|
+
"locker.lineupKicker": "La composition",
|
|
359
|
+
"locker.lineupSub": "Onze titulaires, disposés en {formation}. Touchez un joueur pour voir la fiche.",
|
|
360
|
+
"locker.all": "Tout le vestiaire",
|
|
361
|
+
"locker.half": "Mi-temps",
|
|
362
|
+
"training.programKicker": "7 jours, 7 objectifs",
|
|
363
|
+
"training.program": "Le programme de la semaine",
|
|
364
|
+
"training.day1": "Lun",
|
|
365
|
+
"training.day2": "Mar",
|
|
366
|
+
"training.day3": "Mer",
|
|
367
|
+
"training.day4": "Jeu",
|
|
368
|
+
"training.day5": "Ven",
|
|
369
|
+
"training.day6": "Sam",
|
|
370
|
+
"training.day7": "Dim",
|
|
371
|
+
"training.focus1": "Force",
|
|
372
|
+
"training.focus2": "Cardio",
|
|
373
|
+
"training.focus3": "Mobilité",
|
|
374
|
+
"training.focus4": "Haut du corps",
|
|
375
|
+
"training.focus5": "Bas du corps",
|
|
376
|
+
"training.focus6": "Plein air",
|
|
377
|
+
"training.focus7": "Récup",
|
|
378
|
+
"training.pack": "Le pack de départ",
|
|
379
|
+
"training.total": "Les 3 ensemble",
|
|
380
|
+
"training.saving": "Vous économisez {amount}",
|
|
381
|
+
"training.promise1": "Retours sous 30 jours",
|
|
382
|
+
"training.promise1Sub": "Essayé, pas convaincu : remboursé",
|
|
383
|
+
"training.promise4": "Matériel testé",
|
|
384
|
+
"training.promise4Sub": "Sélectionné pour durer",
|
|
385
|
+
"arena.chrono": "Fin de l'épreuve",
|
|
386
|
+
"arena.podiumKicker": "Les plus fortes remises",
|
|
387
|
+
"arena.podium": "Le podium",
|
|
388
|
+
"arena.place1": "Or",
|
|
389
|
+
"arena.place2": "Argent",
|
|
390
|
+
"arena.place3": "Bronze",
|
|
391
|
+
"arena.all": "Toutes les épreuves",
|
|
392
|
+
"mosaic.left": "Il reste",
|
|
393
|
+
"mosaic.offers": "offres en jeu",
|
|
394
|
+
"mosaic.medalsKicker": "Le classement",
|
|
395
|
+
"mosaic.rank": "Rang",
|
|
396
|
+
"mosaic.product": "Produit",
|
|
397
|
+
"mosaic.saving": "Remise",
|
|
398
|
+
"mosaic.price": "Prix",
|
|
399
|
+
"mosaic.relay": "Le relais",
|
|
400
|
+
"mosaic.leg": "Relais {n}",
|
|
401
|
+
"lantern.until": "Jusqu'à la fin de l'offre",
|
|
402
|
+
"lantern.selectionKicker": "Sous la lanterne",
|
|
403
|
+
"lantern.giftsKicker": "Pour chacun",
|
|
404
|
+
"lantern.gifts": "Idées cadeaux par budget",
|
|
405
|
+
"lantern.tier1": "Petites attentions",
|
|
406
|
+
"lantern.tier2": "Beaux cadeaux",
|
|
407
|
+
"lantern.tier3": "Grands gestes",
|
|
408
|
+
"lantern.upTo": "Jusqu'à {amount}",
|
|
409
|
+
"lantern.from": "À partir de {amount}",
|
|
410
|
+
"lantern.between": "De {min} à {max}",
|
|
411
|
+
"souk.perk1": "Emballage cadeau offert",
|
|
412
|
+
"souk.perk2": "Livré avant la fête",
|
|
413
|
+
"souk.stallsKicker": "Au détour des étals",
|
|
414
|
+
"souk.sign": "Avec nos meilleurs vœux",
|
|
415
|
+
"souk.all": "Tout le souk",
|
|
416
|
+
"shop.results": "Résultats pour « {q} »",
|
|
417
|
+
"shop.clear": "Effacer la recherche",
|
|
418
|
+
"shop.noResults": "Aucun produit ne correspond à « {q} ». Essayez un autre mot.",
|
|
419
|
+
"souk.see": "Voir le produit",
|
|
420
|
+
"checkout.payPaytech": "Wave, Orange Money, Free Money ou carte",
|
|
421
|
+
"checkout.payPaydunya": "Wave, Orange Money ou carte",
|
|
422
|
+
"checkout.payNaboopay": "Wave ou Orange Money",
|
|
423
|
+
"checkout.payRedirect": "Payer maintenant",
|
|
424
|
+
"checkout.payRedirectHint": "Vous allez être redirigé vers la page de paiement sécurisée, puis revenir ici automatiquement.",
|
|
425
|
+
"checkout.payChecking": "Nous confirmons votre paiement…",
|
|
426
|
+
"checkout.payCancelled": "Paiement annulé. Vous pouvez réessayer ou choisir un autre moyen de paiement.",
|
|
427
|
+
"checkout.payRetry": "Vérifier à nouveau",
|
|
428
|
+
"checkout.orderLabel": "Commande en ligne",
|
|
429
|
+
"checkout.payInitError": "Ce moyen de paiement est momentanément indisponible. Réessayez dans un instant ou choisissez-en un autre.",
|
|
430
|
+
"cookies.title": "Cookies",
|
|
431
|
+
"cookies.text": "Ce site utilise uniquement des cookies nécessaires à son fonctionnement (panier, connexion). Aucun suivi publicitaire.",
|
|
432
|
+
"cookies.textOptional": "Nous utilisons des cookies nécessaires au fonctionnement du site et, avec votre accord, des cookies de mesure d’audience.",
|
|
433
|
+
"cookies.more": "En savoir plus",
|
|
434
|
+
"cookies.ok": "J’ai compris",
|
|
435
|
+
"cookies.accept": "Accepter",
|
|
436
|
+
"cookies.refuse": "Refuser",
|
|
437
|
+
"legal.privacy.1.title": "Qui sommes-nous",
|
|
438
|
+
"legal.privacy.1.body": "{storeName} est responsable des données personnelles collectées sur ce site. Pour toute question : {email}.",
|
|
439
|
+
"legal.privacy.2.title": "Les données que nous collectons",
|
|
440
|
+
"legal.privacy.2.body": "Uniquement ce qui est utile à votre commande : nom, adresse de livraison et de facturation, e-mail, téléphone, articles commandés et historique de vos commandes. Si vous créez un compte : votre e-mail et votre mot de passe (enregistré chiffré, jamais lisible, même par nous). Si vous laissez un avis : votre prénom, votre note et votre commentaire.",
|
|
441
|
+
"legal.privacy.3.title": "Pourquoi",
|
|
442
|
+
"legal.privacy.3.body": "Préparer, livrer et suivre votre commande ; vous envoyer les e-mails liés à la commande (confirmation, expédition, livraison, remboursement) ; répondre à vos messages ; respecter nos obligations comptables. Nous ne vendons ni ne louons jamais vos données, et nous ne vous envoyons pas de publicité sans votre accord.",
|
|
443
|
+
"legal.privacy.4.title": "Qui les reçoit",
|
|
444
|
+
"legal.privacy.4.body": "Seulement les prestataires nécessaires, et seulement ce dont ils ont besoin : le transporteur (nom, adresse, téléphone), notre partenaire de paiement (carte, Wave, Orange Money…), notre service d'envoi d'e-mails et notre hébergeur. Aucun autre tiers.",
|
|
445
|
+
"legal.privacy.5.title": "Paiement",
|
|
446
|
+
"legal.privacy.5.body": "Le paiement se fait sur la page sécurisée de notre partenaire de paiement. Nous ne voyons, ne recevons et ne conservons jamais votre numéro de carte ni vos codes Wave ou Orange Money. Le montant payé est toujours calculé par notre serveur, jamais par votre navigateur.",
|
|
447
|
+
"legal.privacy.6.title": "Sécurité",
|
|
448
|
+
"legal.privacy.6.body": "Le site est chiffré (HTTPS) : les informations échangées ne peuvent pas être lues en chemin. Les mots de passe sont enregistrés chiffrés. L'accès à l'administration de la boutique est protégé par identifiant et mot de passe, et réservé à l'équipe de {storeName}. Les données sont sauvegardées régulièrement.",
|
|
449
|
+
"legal.privacy.7.title": "Combien de temps",
|
|
450
|
+
"legal.privacy.7.body": "Les données de commande sont conservées le temps nécessaire à la gestion de la commande, puis le temps imposé par la loi pour les factures (en général 10 ans). Un compte client sans commande depuis 3 ans peut être supprimé. Vous pouvez demander la suppression de votre compte à tout moment.",
|
|
451
|
+
"legal.privacy.8.title": "Cookies",
|
|
452
|
+
"legal.privacy.8.body": "Nous utilisons uniquement les cookies nécessaires au fonctionnement du site : votre panier, votre connexion et la boutique de votre panier. Aucun cookie publicitaire, aucun suivi entre sites, aucune revente.",
|
|
453
|
+
"legal.privacy.9.title": "Vos droits",
|
|
454
|
+
"legal.privacy.9.body": "Vous pouvez à tout moment consulter, corriger, supprimer ou récupérer vos données, et vous opposer à leur utilisation. Écrivez-nous à {email} : nous répondons sous 30 jours. Si vous n'êtes pas satisfait de notre réponse, vous pouvez vous adresser à l'autorité de protection des données de votre pays (par exemple la CDP au Sénégal ou la CNIL en France).",
|
|
455
|
+
"legal.terms.1.title": "Objet",
|
|
456
|
+
"legal.terms.1.body": "Ces conditions s'appliquent à toute commande passée sur le site de {storeName}. Passer commande vaut acceptation de ces conditions.",
|
|
457
|
+
"legal.terms.2.title": "Prix",
|
|
458
|
+
"legal.terms.2.body": "Les prix sont indiqués dans la devise de la boutique, remises comprises au moment de la commande. Le montant débité est celui affiché à l'étape du paiement, frais de livraison inclus. En cas d'erreur de prix manifeste, {storeName} peut annuler la commande : vous êtes alors intégralement remboursé.",
|
|
459
|
+
"legal.terms.3.title": "Commande",
|
|
460
|
+
"legal.terms.3.body": "La commande est confirmée par un e-mail récapitulatif. {storeName} peut refuser une commande en cas de litige de paiement ou de stock épuisé ; vous êtes alors prévenu et remboursé.",
|
|
461
|
+
"legal.terms.4.title": "Paiement",
|
|
462
|
+
"legal.terms.4.body": "Par carte, Wave, Orange Money ou tout autre moyen affiché, sur la page sécurisée de notre partenaire de paiement. La commande est préparée dès que le paiement est confirmé.",
|
|
463
|
+
"legal.terms.5.title": "Livraison",
|
|
464
|
+
"legal.terms.5.body": "Les modes, tarifs et délais sont affichés avant le paiement. Un e-mail vous prévient à l'expédition, avec le numéro de suivi quand il existe. Voir la page Livraison.",
|
|
465
|
+
"legal.terms.6.title": "Retours et remboursements",
|
|
466
|
+
"legal.terms.6.body": "Vous disposez de 30 jours après réception pour retourner un article non utilisé, dans son emballage d'origine. Le remboursement se fait sur le moyen de paiement d'origine. Voir la page Retours.",
|
|
467
|
+
"legal.terms.7.title": "Garanties",
|
|
468
|
+
"legal.terms.7.body": "Les produits bénéficient des garanties légales de conformité. Un article abîmé ou non conforme à la réception ? Écrivez-nous à {email} avec une photo : nous le remplaçons ou le remboursons.",
|
|
469
|
+
"legal.terms.8.title": "Réclamations",
|
|
470
|
+
"legal.terms.8.body": "Pour toute réclamation, contactez-nous d'abord à {email} : nous cherchons toujours une solution à l'amiable. À défaut, le litige relève des tribunaux compétents.",
|
|
471
|
+
"legal.shipping.1.title": "Préparation",
|
|
472
|
+
"legal.shipping.1.body": "Les commandes sont préparées sous 1 à 2 jours ouvrés après confirmation du paiement.",
|
|
473
|
+
"legal.shipping.2.title": "Modes et tarifs",
|
|
474
|
+
"legal.shipping.2.body": "Les modes de livraison disponibles pour votre adresse, leurs tarifs et leurs délais estimés sont affichés avant le paiement. Le prix de la livraison est inclus dans le total payé : aucun frais caché à la livraison.",
|
|
475
|
+
"legal.shipping.3.title": "Suivi",
|
|
476
|
+
"legal.shipping.3.body": "Un e-mail vous prévient quand votre colis part, avec le numéro et le lien de suivi quand le transporteur en fournit un, puis un autre à la livraison. Vous pouvez aussi suivre votre commande depuis la page « Suivi de commande ».",
|
|
477
|
+
"legal.shipping.4.title": "Colis abîmé ou en retard",
|
|
478
|
+
"legal.shipping.4.body": "Un colis abîmé à la réception ou un retard important ? Écrivez-nous à {email} avec votre numéro de commande : nous nous en occupons.",
|
|
479
|
+
"legal.returns.1.title": "Délai",
|
|
480
|
+
"legal.returns.1.body": "Vous pouvez retourner un article sous 30 jours après réception.",
|
|
481
|
+
"legal.returns.2.title": "Conditions",
|
|
482
|
+
"legal.returns.2.body": "L'article doit être non utilisé, complet et dans son emballage d'origine. Pour des raisons d'hygiène, certains articles ne peuvent pas être repris une fois ouverts (cosmétiques, sous-vêtements, denrées alimentaires).",
|
|
483
|
+
"legal.returns.3.title": "Comment faire",
|
|
484
|
+
"legal.returns.3.body": "Écrivez-nous à {email} avec votre numéro de commande et l'article concerné : nous vous indiquons comment le renvoyer.",
|
|
485
|
+
"legal.returns.4.title": "Remboursement",
|
|
486
|
+
"legal.returns.4.body": "Dès réception et vérification de l'article, nous vous remboursons sur le moyen de paiement d'origine (carte, Wave, Orange Money…), et vous recevez un e-mail de confirmation. Un article reçu abîmé ou non conforme est remboursé en totalité, frais de retour compris.",
|
|
487
|
+
"legal.updated": "Dernière mise à jour : {date}",
|
|
488
|
+
"legal.emailFallback": "la page Contact",
|
|
489
|
+
"reviews.outOf": "Note : {value} sur 5",
|
|
490
|
+
"reviews.title": "Avis clients",
|
|
491
|
+
"reviews.none": "Pas encore d'avis sur ce produit. Chaque acheteur reçoit un lien pour le noter après la livraison.",
|
|
492
|
+
"reviews.countOne": "{count} avis",
|
|
493
|
+
"reviews.countMany": "{count} avis",
|
|
494
|
+
"reviews.how": "Avis d'acheteurs vérifiés : seuls les clients qui ont commandé ce produit peuvent le noter, et chaque avis est relu avant publication.",
|
|
495
|
+
"reviews.verified": "Achat vérifié",
|
|
496
|
+
"reviews.homeKicker": "Ils ont commandé",
|
|
497
|
+
"reviews.homeTitle": "Ce que nos clients en pensent",
|
|
498
|
+
"reviews.pageTitle": "Laisser un avis",
|
|
499
|
+
"reviews.pageLead": "Merci pour votre commande n° {number} ! Notez vos articles : votre avis aide les autres clients.",
|
|
500
|
+
"reviews.invalidLink": "Ce lien n'est plus valable. Les liens pour laisser un avis sont envoyés par e-mail à la livraison et restent valables 6 mois.",
|
|
501
|
+
"reviews.backHome": "Retour à la boutique",
|
|
502
|
+
"reviews.already": "Merci, vous avez déjà noté ce produit.",
|
|
503
|
+
"reviews.yourRating": "Votre note",
|
|
504
|
+
"reviews.rating1": "Décevant",
|
|
505
|
+
"reviews.rating2": "Moyen",
|
|
506
|
+
"reviews.rating3": "Bien",
|
|
507
|
+
"reviews.rating4": "Très bien",
|
|
508
|
+
"reviews.rating5": "Excellent",
|
|
509
|
+
"reviews.name": "Votre prénom (affiché avec l'avis)",
|
|
510
|
+
"reviews.titleField": "Titre",
|
|
511
|
+
"reviews.optional": "(facultatif)",
|
|
512
|
+
"reviews.comment": "Votre avis",
|
|
513
|
+
"reviews.commentHint": "Qualité, taille, livraison… ce qui aidera les prochains clients (10 caractères minimum).",
|
|
514
|
+
"reviews.send": "Publier mon avis",
|
|
515
|
+
"reviews.sending": "Envoi…",
|
|
516
|
+
"reviews.thanks": "Merci ! Votre avis sera visible après une rapide relecture.",
|
|
517
|
+
"reviews.error.invalid": "Choisissez une note et écrivez au moins 10 caractères (sans < ni >).",
|
|
518
|
+
"reviews.error.duplicate": "Vous avez déjà noté ce produit pour cette commande.",
|
|
519
|
+
"reviews.error.expired": "Ce lien n'est plus valable.",
|
|
520
|
+
"reviews.error.tooMany": "Trop de tentatives. Réessayez dans un moment.",
|
|
521
|
+
"reviews.error.failed": "L'envoi n'a pas abouti. Réessayez dans un instant."
|
|
335
522
|
}
|
|
@@ -5,7 +5,7 @@ import { Logo } from "@modules/ub/nav"
|
|
|
5
5
|
|
|
6
6
|
export default function CheckoutLayout({ children }: { children: React.ReactNode }) {
|
|
7
7
|
return (
|
|
8
|
-
<div className="w-full relative small:min-h-screen">
|
|
8
|
+
<div className="w-full relative small:min-h-screen ub-page" data-page="checkout">
|
|
9
9
|
<header className="ub-nav">
|
|
10
10
|
<div className="ub-wrap">
|
|
11
11
|
<Link href="/cart" className="ub-more" data-testid="back-to-cart-link">
|
|
@@ -9,6 +9,11 @@ import FreeShippingPriceNudge from "@modules/shipping/components/free-shipping-p
|
|
|
9
9
|
import Footer from "@modules/ub/footer"
|
|
10
10
|
import Nav from "@modules/ub/nav"
|
|
11
11
|
import TopBar from "@modules/ub/top-bar"
|
|
12
|
+
import BoutiqueClosed from "@modules/ub/campaign-ended"
|
|
13
|
+
import { headers } from "next/headers"
|
|
14
|
+
|
|
15
|
+
// Site fermé : restent ouverts le compte, le suivi de commande, le panier et les pages légales
|
|
16
|
+
const OPEN_WHEN_CLOSED = /^\/(account|order|cart|legal|review)(\/|$)/
|
|
12
17
|
|
|
13
18
|
async function CartNotices() {
|
|
14
19
|
const [customer, cart] = await Promise.all([retrieveCustomer(), retrieveCart()])
|
|
@@ -28,6 +33,8 @@ async function CartNotices() {
|
|
|
28
33
|
|
|
29
34
|
export default async function PageLayout(props: { children: React.ReactNode }) {
|
|
30
35
|
const campaign = await getCampaign()
|
|
36
|
+
const path = (await headers()).get("x-ub-path") ?? "/"
|
|
37
|
+
const closed = !campaign.online && !OPEN_WHEN_CLOSED.test(path)
|
|
31
38
|
|
|
32
39
|
return (
|
|
33
40
|
<>
|
|
@@ -36,7 +43,7 @@ export default async function PageLayout(props: { children: React.ReactNode }) {
|
|
|
36
43
|
<Suspense fallback={null}>
|
|
37
44
|
<CartNotices />
|
|
38
45
|
</Suspense>
|
|
39
|
-
{props.children}
|
|
46
|
+
{closed ? <BoutiqueClosed nextUrl={campaign.nextCampaignUrl} /> : props.children}
|
|
40
47
|
<Footer />
|
|
41
48
|
</>
|
|
42
49
|
)
|
|
@@ -9,6 +9,9 @@ type Page = (typeof PAGES)[number]
|
|
|
9
9
|
|
|
10
10
|
type Props = { params: Promise<{ page: string }> }
|
|
11
11
|
|
|
12
|
+
/** Date de la dernière modification des textes légaux (locales/*.json, clés legal.*) : à changer avec eux */
|
|
13
|
+
const LEGAL_UPDATED = "2026-09-30"
|
|
14
|
+
|
|
12
15
|
const isPage = (p: string): p is Page => (PAGES as readonly string[]).includes(p)
|
|
13
16
|
|
|
14
17
|
export async function generateMetadata(props: Props): Promise<Metadata> {
|
|
@@ -21,15 +24,47 @@ export default async function LegalPage(props: Props) {
|
|
|
21
24
|
if (!isPage(page)) notFound()
|
|
22
25
|
if (page === "contact") return <ContactPage />
|
|
23
26
|
|
|
24
|
-
const { storeName } = getBoutique()
|
|
27
|
+
const { storeName, merchantEmail, locale } = getBoutique()
|
|
28
|
+
const vars = { storeName, email: merchantEmail ?? t("legal.emailFallback") }
|
|
29
|
+
|
|
30
|
+
// Sections « legal.<page>.<n>.title / .body » (locales) ; à défaut, l'ancien paragraphe unique
|
|
31
|
+
const sections: { title: string; body: string }[] = []
|
|
32
|
+
for (let n = 1; n <= 20; n++) {
|
|
33
|
+
const key = `legal.${page}.${n}.title`
|
|
34
|
+
const title = t(key, vars)
|
|
35
|
+
if (title === key) break
|
|
36
|
+
sections.push({ title, body: t(`legal.${page}.${n}.body`, vars) })
|
|
37
|
+
}
|
|
38
|
+
const updated = new Intl.DateTimeFormat(locale, { dateStyle: "long" }).format(new Date(LEGAL_UPDATED))
|
|
25
39
|
|
|
26
40
|
return (
|
|
27
|
-
<section className="ub-section">
|
|
28
|
-
<div className="ub-wrap"
|
|
29
|
-
<h1 className="ub-head"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
41
|
+
<section className="ub-section ub-legal">
|
|
42
|
+
<div className="ub-wrap">
|
|
43
|
+
<h1 className="ub-head ub-legal__title">{t(`legal.${page}`)}</h1>
|
|
44
|
+
<p className="ub-legal__updated">{t("legal.updated", { date: updated })}</p>
|
|
45
|
+
{sections.length ? (
|
|
46
|
+
<>
|
|
47
|
+
{sections.length > 3 && (
|
|
48
|
+
<nav className="ub-legal__toc" aria-label={t(`legal.${page}`)}>
|
|
49
|
+
<ol>
|
|
50
|
+
{sections.map((s, i) => (
|
|
51
|
+
<li key={s.title}>
|
|
52
|
+
<a href={`#s${i + 1}`}>{s.title}</a>
|
|
53
|
+
</li>
|
|
54
|
+
))}
|
|
55
|
+
</ol>
|
|
56
|
+
</nav>
|
|
57
|
+
)}
|
|
58
|
+
{sections.map((s, i) => (
|
|
59
|
+
<section key={s.title} id={`s${i + 1}`} className="ub-legal__sec">
|
|
60
|
+
<h2>{s.title}</h2>
|
|
61
|
+
<p>{s.body}</p>
|
|
62
|
+
</section>
|
|
63
|
+
))}
|
|
64
|
+
</>
|
|
65
|
+
) : (
|
|
66
|
+
<p>{t(`legal.${page}.body`, vars)}</p>
|
|
67
|
+
)}
|
|
33
68
|
</div>
|
|
34
69
|
</section>
|
|
35
70
|
)
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { Suspense } from "react"
|
|
2
2
|
import { connection } from "next/server"
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { getCampaign } from "@lib/boutique/campaign"
|
|
5
5
|
import { t } from "@lib/boutique/i18n"
|
|
6
6
|
import { listCategories } from "@lib/data/categories"
|
|
7
7
|
import { listProducts } from "@lib/data/products"
|
|
8
8
|
import { Hero, PromoBanner, SectionHead, TrustBar } from "@modules/ub/campaign-blocks"
|
|
9
|
-
import CampaignEnded from "@modules/ub/campaign-ended"
|
|
10
9
|
import CategoryTiles from "@modules/ub/category-tiles"
|
|
11
10
|
import { ProductGrid } from "@modules/ub/product-card"
|
|
12
11
|
import { DealsFold, DealsSpotlight } from "@modules/ub/showcase"
|
|
@@ -16,7 +15,17 @@ import { CinemaHero, CinemaRow, CinemaScreening, CinemaTop } from "@modules/ub/c
|
|
|
16
15
|
import { TechBento, TechFinal, TechFlash, TechGrid, TechHero, TechSpecs, TechTicker } from "@modules/ub/tech"
|
|
17
16
|
import { GrimoireHero, GrimoireShelf, GrimoireSpell, GrimoireTarot } from "@modules/ub/grimoire"
|
|
18
17
|
import { AlbumGarland, AlbumHero, AlbumPolaroids, AlbumStory, AlbumTicket } from "@modules/ub/album"
|
|
18
|
+
import { RivieraColours, RivieraHero, RivieraLookbook, RivieraTailor } from "@modules/ub/riviera"
|
|
19
|
+
import { SunsetBand, SunsetChecklistSection, SunsetHero, SunsetPostcards } from "@modules/ub/sunset"
|
|
20
|
+
import { LockerHalftime, LockerHero, LockerLineup, LockerRack } from "@modules/ub/locker"
|
|
21
|
+
import { TrainingHero, TrainingPack, TrainingProgram, TrainingPromise, TrainingTop } from "@modules/ub/training"
|
|
22
|
+
import { ArenaHero, ArenaLanes, ArenaPodium, ArenaRecord } from "@modules/ub/arena"
|
|
23
|
+
import { MosaicDisciplines, MosaicHero, MosaicMedals, MosaicRelay } from "@modules/ub/mosaic"
|
|
24
|
+
import { LanternArches, LanternBudget, LanternHero, LanternNight } from "@modules/ub/lantern"
|
|
25
|
+
import { SoukHero, SoukLetter, SoukStalls, SoukWho } from "@modules/ub/souk"
|
|
26
|
+
import { discountOf } from "@modules/ub/home-kit"
|
|
19
27
|
import { getBoutique } from "@lib/boutique/config"
|
|
28
|
+
import { HomeReviews } from "@modules/ub/reviews"
|
|
20
29
|
import type { Campaign } from "@lib/boutique/campaign"
|
|
21
30
|
|
|
22
31
|
async function Categories({ title }: { title: string }) {
|
|
@@ -188,16 +197,152 @@ async function GalaProducts({ campaign }: { campaign: Campaign }) {
|
|
|
188
197
|
)
|
|
189
198
|
}
|
|
190
199
|
|
|
200
|
+
/** Produits de l'accueil : tels quels et triés par remise (plus forte d'abord). */
|
|
201
|
+
async function homeProducts(limit = 12) {
|
|
202
|
+
const {
|
|
203
|
+
response: { products },
|
|
204
|
+
} = await listProducts({ queryParams: { limit, fields: "*categories,*collection" } })
|
|
205
|
+
return { products, sorted: [...products].sort((a, b) => discountOf(b) - discountOf(a)) }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Accueil « Riviera » (Summer). */
|
|
209
|
+
async function RivieraPage({ campaign }: { campaign: Campaign }) {
|
|
210
|
+
const { products } = await homeProducts()
|
|
211
|
+
return (
|
|
212
|
+
<>
|
|
213
|
+
<RivieraLookbook products={products} title={campaign.sections?.products ?? t("section.products")} />
|
|
214
|
+
<RivieraColours products={products} />
|
|
215
|
+
<RivieraTailor campaign={campaign} />
|
|
216
|
+
</>
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Accueil « Sunset » (Summer 2) : cartes postales des plus fortes remises, liste de plage interactive. */
|
|
221
|
+
async function SunsetPage({ campaign }: { campaign: Campaign }) {
|
|
222
|
+
const { products, sorted } = await homeProducts()
|
|
223
|
+
return (
|
|
224
|
+
<>
|
|
225
|
+
<SunsetPostcards products={sorted} campaign={campaign} title={campaign.sections?.products ?? t("section.products")} />
|
|
226
|
+
<SunsetChecklistSection products={products} />
|
|
227
|
+
<SunsetBand campaign={campaign} />
|
|
228
|
+
</>
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Accueil « Vestiaire » (Sport). */
|
|
233
|
+
async function LockerPage({ campaign }: { campaign: Campaign }) {
|
|
234
|
+
const { products, sorted } = await homeProducts()
|
|
235
|
+
return (
|
|
236
|
+
<>
|
|
237
|
+
<LockerLineup products={sorted} title={campaign.sections?.products ?? t("section.products")} />
|
|
238
|
+
<LockerRack products={products} title={campaign.sections?.categories ?? t("section.categories")} />
|
|
239
|
+
<LockerHalftime campaign={campaign} />
|
|
240
|
+
</>
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Accueil « Training » (Sport 2) : les plus choisis juste sous le hero. */
|
|
245
|
+
async function TrainingPage({ campaign }: { campaign: Campaign }) {
|
|
246
|
+
const { products, sorted } = await homeProducts()
|
|
247
|
+
return (
|
|
248
|
+
<>
|
|
249
|
+
<TrainingTop products={sorted} title={campaign.sections?.products ?? t("section.products")} />
|
|
250
|
+
<TrainingProgram products={products} />
|
|
251
|
+
<TrainingPack campaign={campaign} products={sorted.slice(4)} />
|
|
252
|
+
<TrainingPromise freeShipping={campaign.freeShipping} />
|
|
253
|
+
</>
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Accueil « Arène » (Jeux) : podium des 3 plus fortes remises, puis les couloirs. */
|
|
258
|
+
async function ArenaPage({ campaign }: { campaign: Campaign }) {
|
|
259
|
+
const { sorted } = await homeProducts()
|
|
260
|
+
return (
|
|
261
|
+
<>
|
|
262
|
+
<ArenaPodium products={sorted} />
|
|
263
|
+
<ArenaLanes products={sorted} title={campaign.sections?.products ?? t("section.products")} />
|
|
264
|
+
<ArenaRecord campaign={campaign} />
|
|
265
|
+
</>
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** Accueil « Mosaïque » (Jeux 2). */
|
|
270
|
+
async function MosaicPage({ campaign }: { campaign: Campaign }) {
|
|
271
|
+
const [{ products, sorted }, categories] = await Promise.all([homeProducts(), listCategories().catch(() => [])])
|
|
272
|
+
return (
|
|
273
|
+
<>
|
|
274
|
+
<MosaicDisciplines categories={categories} title={campaign.sections?.categories ?? t("section.categories")} />
|
|
275
|
+
<MosaicMedals products={sorted} title={campaign.sections?.products ?? t("section.products")} />
|
|
276
|
+
<MosaicRelay campaign={campaign} products={products} />
|
|
277
|
+
</>
|
|
278
|
+
)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** Accueil « Lanterne » (Mubarak · Nuit) : la sélection (plus fortes remises), idées cadeaux par budget. */
|
|
282
|
+
async function LanternPage({ campaign }: { campaign: Campaign }) {
|
|
283
|
+
const { products, sorted } = await homeProducts()
|
|
284
|
+
return (
|
|
285
|
+
<>
|
|
286
|
+
<LanternArches products={sorted} title={campaign.sections?.products ?? t("section.products")} />
|
|
287
|
+
<LanternBudget products={products} />
|
|
288
|
+
<LanternNight campaign={campaign} />
|
|
289
|
+
</>
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Accueil « Souk » (Mubarak · Sable). */
|
|
294
|
+
async function SoukPage({ campaign }: { campaign: Campaign }) {
|
|
295
|
+
const [{ sorted }, categories] = await Promise.all([homeProducts(), listCategories().catch(() => [])])
|
|
296
|
+
return (
|
|
297
|
+
<>
|
|
298
|
+
<SoukStalls products={sorted} title={campaign.sections?.products ?? t("section.products")} />
|
|
299
|
+
<SoukWho categories={categories} title={campaign.sections?.categories ?? t("section.categories")} />
|
|
300
|
+
<SoukLetter campaign={campaign} />
|
|
301
|
+
</>
|
|
302
|
+
)
|
|
303
|
+
}
|
|
304
|
+
|
|
191
305
|
function GridFallback() {
|
|
192
306
|
return <div className="ub-section" aria-busy="true" style={{ minHeight: 320 }} />
|
|
193
307
|
}
|
|
194
308
|
|
|
309
|
+
/** Accueil : la mise en page du thème, puis les derniers avis clients (section commune, aux couleurs du thème). */
|
|
195
310
|
export default async function Home() {
|
|
311
|
+
return (
|
|
312
|
+
<>
|
|
313
|
+
<HomeBody />
|
|
314
|
+
<Suspense fallback={null}>
|
|
315
|
+
<HomeReviews />
|
|
316
|
+
</Suspense>
|
|
317
|
+
</>
|
|
318
|
+
)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async function HomeBody() {
|
|
196
322
|
await connection()
|
|
197
323
|
const campaign = await getCampaign()
|
|
198
324
|
|
|
199
|
-
|
|
200
|
-
|
|
325
|
+
|
|
326
|
+
const home = getBoutique().theme.home
|
|
327
|
+
const themed: Partial<Record<string, [string, React.ReactNode, React.ReactNode]>> = {
|
|
328
|
+
riviera: ["ub-riviera", <RivieraHero key="h" campaign={campaign} />, <RivieraPage campaign={campaign} />],
|
|
329
|
+
sunset: ["ub-sunset", <SunsetHero key="h" campaign={campaign} />, <SunsetPage campaign={campaign} />],
|
|
330
|
+
locker: ["ub-locker", <LockerHero key="h" campaign={campaign} />, <LockerPage campaign={campaign} />],
|
|
331
|
+
training: ["ub-training", <TrainingHero key="h" campaign={campaign} />, <TrainingPage campaign={campaign} />],
|
|
332
|
+
arena: ["ub-arena", <ArenaHero key="h" campaign={campaign} />, <ArenaPage campaign={campaign} />],
|
|
333
|
+
mosaic: ["ub-mosaic", <MosaicHero key="h" campaign={campaign} />, <MosaicPage campaign={campaign} />],
|
|
334
|
+
lantern: ["ub-lantern", <LanternHero key="h" campaign={campaign} />, <LanternPage campaign={campaign} />],
|
|
335
|
+
souk: ["ub-souk", <SoukHero key="h" campaign={campaign} />, <SoukPage campaign={campaign} />],
|
|
336
|
+
}
|
|
337
|
+
const page = home ? themed[home] : undefined
|
|
338
|
+
if (page) {
|
|
339
|
+
const [cls, hero, rest] = page
|
|
340
|
+
return (
|
|
341
|
+
<div className={cls}>
|
|
342
|
+
{hero}
|
|
343
|
+
<Suspense fallback={<GridFallback />}>{rest}</Suspense>
|
|
344
|
+
</div>
|
|
345
|
+
)
|
|
201
346
|
}
|
|
202
347
|
|
|
203
348
|
if (getBoutique().theme.home === "grimoire") {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Metadata } from "next"
|
|
2
|
+
import Link from "next/link"
|
|
3
|
+
import { t } from "@lib/boutique/i18n"
|
|
4
|
+
import { getReviewOrder } from "@lib/data/reviews"
|
|
5
|
+
import FadeImg from "@modules/ub/fade-img"
|
|
6
|
+
import ReviewForm from "@modules/ub/review-form"
|
|
7
|
+
|
|
8
|
+
export async function generateMetadata(): Promise<Metadata> {
|
|
9
|
+
return { title: t("reviews.pageTitle"), robots: { index: false, follow: false } }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type Props = { searchParams: Promise<{ token?: string }> }
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* « Laisser un avis » : ouverte depuis le lien de l'e-mail « commande livrée » (jeton signé).
|
|
16
|
+
* Un formulaire par produit de la commande ; les produits déjà notés sont indiqués. Page non indexée.
|
|
17
|
+
*/
|
|
18
|
+
export default async function ReviewPage(props: Props) {
|
|
19
|
+
const { token = "" } = await props.searchParams
|
|
20
|
+
const order = token ? await getReviewOrder(token) : null
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<section className="ub-section ub-reviewpage">
|
|
24
|
+
<div className="ub-wrap">
|
|
25
|
+
<h1 className="ub-head ub-reviewpage__title">{t("reviews.pageTitle")}</h1>
|
|
26
|
+
{!order ? (
|
|
27
|
+
<>
|
|
28
|
+
<p className="ub-reviewpage__lead">{t("reviews.invalidLink")}</p>
|
|
29
|
+
<Link href="/" className="ub-rform__btn">
|
|
30
|
+
{t("reviews.backHome")}
|
|
31
|
+
</Link>
|
|
32
|
+
</>
|
|
33
|
+
) : (
|
|
34
|
+
<>
|
|
35
|
+
<p className="ub-reviewpage__lead">{t("reviews.pageLead", { number: String(order.number) })}</p>
|
|
36
|
+
<ul className="ub-reviewpage__list">
|
|
37
|
+
{order.products.map((p) => (
|
|
38
|
+
<li key={p.product_id} className="ub-reviewpage__item">
|
|
39
|
+
<div className="ub-reviewpage__product">
|
|
40
|
+
{p.thumbnail && (
|
|
41
|
+
<span className="ub-reviewpage__img">
|
|
42
|
+
<FadeImg src={p.thumbnail} alt="" />
|
|
43
|
+
</span>
|
|
44
|
+
)}
|
|
45
|
+
<b>{p.title}</b>
|
|
46
|
+
</div>
|
|
47
|
+
{p.reviewed ? (
|
|
48
|
+
<p className="ub-rform__done">{t("reviews.already")}</p>
|
|
49
|
+
) : (
|
|
50
|
+
<ReviewForm token={token} productId={p.product_id} firstName={order.first_name} />
|
|
51
|
+
)}
|
|
52
|
+
</li>
|
|
53
|
+
))}
|
|
54
|
+
</ul>
|
|
55
|
+
</>
|
|
56
|
+
)}
|
|
57
|
+
</div>
|
|
58
|
+
</section>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
@@ -13,18 +13,20 @@ type Params = {
|
|
|
13
13
|
sortBy?: SortOptions
|
|
14
14
|
page?: string
|
|
15
15
|
category?: string
|
|
16
|
+
q?: string
|
|
16
17
|
}>
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export default async function StorePage(props: Params) {
|
|
20
21
|
const searchParams = await props.searchParams;
|
|
21
|
-
const { sortBy, page, category } = searchParams
|
|
22
|
+
const { sortBy, page, category, q } = searchParams
|
|
22
23
|
|
|
23
24
|
return (
|
|
24
25
|
<StoreTemplate
|
|
25
26
|
sortBy={sortBy}
|
|
26
27
|
page={page}
|
|
27
28
|
category={category}
|
|
29
|
+
q={typeof q === "string" ? q.slice(0, 100) : undefined}
|
|
28
30
|
/>
|
|
29
31
|
)
|
|
30
32
|
}
|