@salifbiaye/create-boutique 0.5.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 +241 -17
- package/cli/dist/create.js +108 -15
- package/cli/dist/generate.js +108 -39
- package/cli/dist/guide.js +12 -4
- package/cli/dist/index.js +38 -7
- package/cli/dist/payments.js +150 -0
- package/cli/dist/project.js +42 -3
- 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 +63 -5
- package/templates/backend/src/admin/i18n/json/fr.json +63 -5
- package/templates/backend/src/admin/routes/boutique-campaigns/campaign-drawer.tsx +29 -17
- package/templates/backend/src/admin/routes/boutique-campaigns/page.tsx +84 -6
- package/templates/backend/src/admin/routes/reviews/page.tsx +259 -0
- package/templates/backend/src/api/admin/active-boutique/route.ts +25 -0
- package/templates/backend/src/api/admin/boutique-campaigns/[salesChannelId]/route.ts +7 -1
- package/templates/backend/src/api/admin/boutique-campaigns/route.ts +9 -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 +9 -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/active.ts +41 -0
- 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 +38 -1
- 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/rewrite-file-urls.ts +56 -0
- package/templates/backend/src/scripts/seed.ts +60 -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)/products/[handle]/page.tsx +4 -1
- 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/config.ts +71 -23
- package/templates/storefront/src/lib/boutique/page-name.ts +25 -0
- package/templates/storefront/src/lib/boutique/schema.ts +7 -2
- package/templates/storefront/src/lib/config.ts +37 -8
- package/templates/storefront/src/lib/constants.tsx +17 -0
- package/templates/storefront/src/lib/data/cart.ts +4 -1
- package/templates/storefront/src/lib/data/cookies.ts +14 -2
- 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/checkout/components/shipping-address/index.tsx +6 -1
- 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 +4 -1
- 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 -2
- 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 +5 -8
- 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
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salifbiaye/create-boutique",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Usine à Boutiques : génère un projet complet (backend Medusa + une boutique Next.js par campagne) prêt pour docker compose",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"docs/fiche-client.html",
|
|
29
29
|
"docs/fiche-client.en.html",
|
|
30
30
|
"docs/guide-complet.fr.html",
|
|
31
|
-
"docs/guide-complet.en.html"
|
|
31
|
+
"docs/guide-complet.en.html",
|
|
32
|
+
"docs/guide-images"
|
|
32
33
|
],
|
|
33
34
|
"scripts": {
|
|
34
35
|
"build": "tsc -p cli",
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { defineConfig, loadEnv } from "@medusajs/framework/utils"
|
|
2
|
+
import { emailMode } from "./src/lib/email-config"
|
|
3
|
+
import { paymentProviders } from "./src/lib/payment-config"
|
|
2
4
|
|
|
3
5
|
loadEnv(process.env.NODE_ENV || "development", process.cwd())
|
|
4
6
|
|
|
@@ -7,7 +9,6 @@ loadEnv(process.env.NODE_ENV || "development", process.cwd())
|
|
|
7
9
|
* Tout vient de l'environnement (docker-compose + .env) : aucun réglage propre à un client ici.
|
|
8
10
|
*/
|
|
9
11
|
const REDIS_URL = process.env.REDIS_URL
|
|
10
|
-
const STRIPE_API_KEY = process.env.STRIPE_API_KEY
|
|
11
12
|
|
|
12
13
|
const modules: Record<string, unknown>[] = [
|
|
13
14
|
{
|
|
@@ -39,42 +40,47 @@ const modules: Record<string, unknown>[] = [
|
|
|
39
40
|
},
|
|
40
41
|
},
|
|
41
42
|
{
|
|
43
|
+
// Paiement manuel toujours actif ; Stripe, PayTech, PayDunya, NabooPay selon les clés du .env (src/lib/payment-config.ts)
|
|
42
44
|
resolve: "@medusajs/medusa/payment",
|
|
43
|
-
options: {
|
|
44
|
-
providers: STRIPE_API_KEY
|
|
45
|
-
? [
|
|
46
|
-
{
|
|
47
|
-
resolve: "@medusajs/medusa/payment-stripe",
|
|
48
|
-
id: "stripe",
|
|
49
|
-
options: {
|
|
50
|
-
apiKey: STRIPE_API_KEY,
|
|
51
|
-
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET || undefined,
|
|
52
|
-
capture: true,
|
|
53
|
-
automaticPaymentMethods: true,
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
]
|
|
57
|
-
: [],
|
|
58
|
-
},
|
|
45
|
+
options: { providers: paymentProviders().map(({ resolve, id, options }) => ({ resolve, id, options })) },
|
|
59
46
|
},
|
|
60
47
|
{
|
|
61
48
|
// Textes, dates et visuel de campagne de chaque boutique, modifiables dans l'admin (« Campagnes »).
|
|
62
49
|
resolve: "./src/modules/campaign",
|
|
63
50
|
},
|
|
51
|
+
{
|
|
52
|
+
// Avis clients (acheteurs vérifiés, validés dans l'admin → Avis)
|
|
53
|
+
resolve: "./src/modules/product-review",
|
|
54
|
+
},
|
|
64
55
|
]
|
|
65
56
|
|
|
66
|
-
// E-mails aux clients et au marchand : une
|
|
67
|
-
//
|
|
68
|
-
|
|
57
|
+
// E-mails aux clients et au marchand (src/lib/email-config.ts) : Resend (une clé pour tous les projets, prioritaire)
|
|
58
|
+
// ou un serveur SMTP (Gmail + mot de passe d'application, OVH, Brevo…). Aucun des deux : aucun e-mail, la boutique fonctionne.
|
|
59
|
+
const mail = emailMode()
|
|
60
|
+
if (mail) {
|
|
69
61
|
modules.push({
|
|
70
62
|
resolve: "@medusajs/medusa/notification",
|
|
71
63
|
options: {
|
|
72
64
|
providers: [
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
65
|
+
mail === "resend"
|
|
66
|
+
? {
|
|
67
|
+
resolve: "./src/modules/resend",
|
|
68
|
+
id: "resend",
|
|
69
|
+
options: { channels: ["email"], api_key: process.env.RESEND_API_KEY, from: process.env.EMAIL_FROM },
|
|
70
|
+
}
|
|
71
|
+
: {
|
|
72
|
+
resolve: "./src/modules/smtp",
|
|
73
|
+
id: "smtp",
|
|
74
|
+
options: {
|
|
75
|
+
channels: ["email"],
|
|
76
|
+
host: process.env.SMTP_HOST,
|
|
77
|
+
port: Number(process.env.SMTP_PORT || 465),
|
|
78
|
+
user: process.env.SMTP_USER,
|
|
79
|
+
pass: process.env.SMTP_PASS,
|
|
80
|
+
// Gmail : l'adresse « De : » doit être celle du compte
|
|
81
|
+
from: process.env.EMAIL_FROM || process.env.SMTP_USER,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
78
84
|
],
|
|
79
85
|
},
|
|
80
86
|
})
|
|
@@ -14,11 +14,13 @@
|
|
|
14
14
|
"@medusajs/framework": "2.21.2",
|
|
15
15
|
"@medusajs/medusa": "2.21.2",
|
|
16
16
|
"csv-parse": "7.0.3",
|
|
17
|
+
"nodemailer": "10.0.13",
|
|
17
18
|
"resend": "6.30.0"
|
|
18
19
|
},
|
|
19
20
|
"devDependencies": {
|
|
20
21
|
"@swc/core": "1.16.2",
|
|
21
22
|
"@types/node": "24.19.0",
|
|
23
|
+
"@types/nodemailer": "8.0.2",
|
|
22
24
|
"@types/react": "18.3.31",
|
|
23
25
|
"@types/react-dom": "18.3.7",
|
|
24
26
|
"prop-types": "15.8.1",
|
|
@@ -8462,6 +8464,16 @@
|
|
|
8462
8464
|
"undici-types": ">=7.24.0 <7.24.7"
|
|
8463
8465
|
}
|
|
8464
8466
|
},
|
|
8467
|
+
"node_modules/@types/nodemailer": {
|
|
8468
|
+
"version": "8.0.2",
|
|
8469
|
+
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-8.0.2.tgz",
|
|
8470
|
+
"integrity": "sha512-c7M5ox8p0nEOfbJ2E9Qcmt8/QCu/VzsiAhzBiZbvky2PhKZDHpKB3sQHBM5MEZNkCfBOaS6S9//AGPRTNB7IaA==",
|
|
8471
|
+
"dev": true,
|
|
8472
|
+
"license": "MIT",
|
|
8473
|
+
"dependencies": {
|
|
8474
|
+
"@types/node": "*"
|
|
8475
|
+
}
|
|
8476
|
+
},
|
|
8465
8477
|
"node_modules/@types/pg": {
|
|
8466
8478
|
"version": "8.6.1",
|
|
8467
8479
|
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz",
|
|
@@ -12958,6 +12970,15 @@
|
|
|
12958
12970
|
"node": ">=6"
|
|
12959
12971
|
}
|
|
12960
12972
|
},
|
|
12973
|
+
"node_modules/nodemailer": {
|
|
12974
|
+
"version": "10.0.13",
|
|
12975
|
+
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-10.0.13.tgz",
|
|
12976
|
+
"integrity": "sha512-SzG86OlvcW/NNhUFC6uROMwRTL4n7MswfQqC/T8mhkmnY1YVa23zUEMYi4ijSeXSl9GLz9ZeTJDUatEDuY5FeQ==",
|
|
12977
|
+
"license": "MIT-0",
|
|
12978
|
+
"engines": {
|
|
12979
|
+
"node": ">=20.0.0"
|
|
12980
|
+
}
|
|
12981
|
+
},
|
|
12961
12982
|
"node_modules/normalize-path": {
|
|
12962
12983
|
"version": "3.0.0",
|
|
12963
12984
|
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
|
@@ -16,11 +16,13 @@
|
|
|
16
16
|
"@medusajs/framework": "2.21.2",
|
|
17
17
|
"@medusajs/medusa": "2.21.2",
|
|
18
18
|
"csv-parse": "7.0.3",
|
|
19
|
+
"nodemailer": "10.0.13",
|
|
19
20
|
"resend": "6.30.0"
|
|
20
21
|
},
|
|
21
22
|
"devDependencies": {
|
|
22
23
|
"@swc/core": "1.16.2",
|
|
23
24
|
"@types/node": "24.19.0",
|
|
25
|
+
"@types/nodemailer": "8.0.2",
|
|
24
26
|
"@types/react": "18.3.31",
|
|
25
27
|
"@types/react-dom": "18.3.7",
|
|
26
28
|
"prop-types": "15.8.1",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"campaigns": {
|
|
3
|
-
"nav": "
|
|
4
|
-
"title": "
|
|
5
|
-
"description": "
|
|
3
|
+
"nav": "My stores",
|
|
4
|
+
"title": "My stores",
|
|
5
|
+
"description": "Your stores: site online or closed, texts, dates and visuals of each campaign. Changes appear on the site within a minute.",
|
|
6
6
|
"store": "Store",
|
|
7
7
|
"dates": "Dates",
|
|
8
8
|
"status": "Status",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"saved": "Campaign saved",
|
|
17
17
|
"reset": "Reset to theme texts",
|
|
18
18
|
"resetDone": "Theme texts restored",
|
|
19
|
-
"emptyHint": "
|
|
19
|
+
"emptyHint": "Fields show the current texts (the theme ones by default): change what you want. {discount}, {endDate} and {freeShipping} are replaced automatically by the discount, end date and free-shipping threshold.",
|
|
20
20
|
"sections": {
|
|
21
21
|
"dates": "Dates",
|
|
22
22
|
"hero": "Hero",
|
|
@@ -66,6 +66,64 @@
|
|
|
66
66
|
"save": "Save image",
|
|
67
67
|
"saved": "Image saved"
|
|
68
68
|
},
|
|
69
|
-
"shareHint": "What shows when the store link is shared (WhatsApp, Facebook, LinkedIn…). Empty = store name, subtitle and main visual."
|
|
69
|
+
"shareHint": "What shows when the store link is shared (WhatsApp, Facebook, LinkedIn…). Empty = store name, subtitle and main visual.",
|
|
70
|
+
"online": {
|
|
71
|
+
"column": "Site",
|
|
72
|
+
"live": "Live",
|
|
73
|
+
"action": "Put live",
|
|
74
|
+
"hint": "Single site: the “Live” store is the one customers see. Orders and in-progress carts of the other stores are not lost.",
|
|
75
|
+
"confirmTitle": "Change the live store?",
|
|
76
|
+
"confirmText": "The site will switch from {{from}} to {{to}}. Customers paying right now can finish their order.",
|
|
77
|
+
"confirm": "Put live",
|
|
78
|
+
"done": "Store is live: the site has switched."
|
|
79
|
+
},
|
|
80
|
+
"site": {
|
|
81
|
+
"offline": "Offline",
|
|
82
|
+
"open": "Open",
|
|
83
|
+
"close": "Close",
|
|
84
|
+
"openTitle": "Open the {{slug}} site?",
|
|
85
|
+
"closeTitle": "Close the {{slug}} site?",
|
|
86
|
+
"openText": "The store becomes visible to everyone again (within a minute).",
|
|
87
|
+
"closeText": "Visitors will see “Store closed”. Customers paying right now can finish their order; account and order tracking stay available. Orders are not affected.",
|
|
88
|
+
"opened": "Site opened.",
|
|
89
|
+
"closed": "Site closed."
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"reviews": {
|
|
93
|
+
"nav": "Reviews",
|
|
94
|
+
"title": "Customer reviews",
|
|
95
|
+
"description": "Reviews left by buyers (link in the « order delivered » e-mail). A review only shows on the stores once approved.",
|
|
96
|
+
"allBoutiques": "All stores",
|
|
97
|
+
"status": {
|
|
98
|
+
"pending": "Pending",
|
|
99
|
+
"approved": "Approved",
|
|
100
|
+
"rejected": "Rejected",
|
|
101
|
+
"all": "All"
|
|
102
|
+
},
|
|
103
|
+
"col": {
|
|
104
|
+
"product": "Product · store",
|
|
105
|
+
"review": "Review",
|
|
106
|
+
"author": "Customer",
|
|
107
|
+
"status": "Status",
|
|
108
|
+
"actions": "Actions"
|
|
109
|
+
},
|
|
110
|
+
"approve": "Approve",
|
|
111
|
+
"reject": "Reject",
|
|
112
|
+
"delete": "Delete",
|
|
113
|
+
"cancel": "Cancel",
|
|
114
|
+
"deleteTitle": "Delete this review?",
|
|
115
|
+
"deleteText": "The review by {{name}} will be permanently removed from the stores.",
|
|
116
|
+
"deletedProduct": "Deleted product",
|
|
117
|
+
"empty": "No review for this filter.",
|
|
118
|
+
"emptyPending": "No pending review: all caught up.",
|
|
119
|
+
"range": "{{from}}–{{to}} of {{count}}",
|
|
120
|
+
"prev": "Previous",
|
|
121
|
+
"next": "Next",
|
|
122
|
+
"toast": {
|
|
123
|
+
"approved": "Review approved: it now shows on the stores",
|
|
124
|
+
"rejected": "Review rejected: it will not show",
|
|
125
|
+
"pending": "Review back to pending",
|
|
126
|
+
"deleted": "Review deleted"
|
|
127
|
+
}
|
|
70
128
|
}
|
|
71
129
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"campaigns": {
|
|
3
|
-
"nav": "
|
|
4
|
-
"title": "
|
|
5
|
-
"description": "
|
|
3
|
+
"nav": "Mes boutiques",
|
|
4
|
+
"title": "Mes boutiques",
|
|
5
|
+
"description": "Vos boutiques : site en ligne ou fermé, textes, dates et visuels de chaque campagne. Les modifications apparaissent sur le site en une minute.",
|
|
6
6
|
"store": "Boutique",
|
|
7
7
|
"dates": "Dates",
|
|
8
8
|
"status": "Statut",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"saved": "Campagne enregistrée",
|
|
17
17
|
"reset": "Réinitialiser aux textes du thème",
|
|
18
18
|
"resetDone": "Textes du thème rétablis",
|
|
19
|
-
"emptyHint": "
|
|
19
|
+
"emptyHint": "Les champs affichent les textes actuels (ceux du thème par défaut) : modifiez ce que vous voulez. {discount}, {endDate} et {freeShipping} sont remplacés automatiquement par la remise, la date de fin et le seuil de livraison.",
|
|
20
20
|
"sections": {
|
|
21
21
|
"dates": "Dates",
|
|
22
22
|
"hero": "Bannière principale",
|
|
@@ -66,6 +66,64 @@
|
|
|
66
66
|
"save": "Enregistrer l'image",
|
|
67
67
|
"saved": "Image enregistrée"
|
|
68
68
|
},
|
|
69
|
-
"shareHint": "Ce qui s'affiche quand on partage le lien de la boutique (WhatsApp, Facebook, LinkedIn…). Vide = nom de la boutique, sous-titre et visuel principal."
|
|
69
|
+
"shareHint": "Ce qui s'affiche quand on partage le lien de la boutique (WhatsApp, Facebook, LinkedIn…). Vide = nom de la boutique, sous-titre et visuel principal.",
|
|
70
|
+
"online": {
|
|
71
|
+
"column": "Site",
|
|
72
|
+
"live": "En ligne",
|
|
73
|
+
"action": "Mettre en ligne",
|
|
74
|
+
"hint": "Un seul site : la boutique « En ligne » est celle que voient les clients. Les commandes et paniers en cours des autres boutiques ne sont pas perdus.",
|
|
75
|
+
"confirmTitle": "Changer la boutique en ligne ?",
|
|
76
|
+
"confirmText": "Le site va passer de {{from}} à {{to}}. Les clients qui paient en ce moment peuvent finir leur commande.",
|
|
77
|
+
"confirm": "Mettre en ligne",
|
|
78
|
+
"done": "Boutique en ligne : le site a changé."
|
|
79
|
+
},
|
|
80
|
+
"site": {
|
|
81
|
+
"offline": "Hors ligne",
|
|
82
|
+
"open": "Ouvrir",
|
|
83
|
+
"close": "Fermer",
|
|
84
|
+
"openTitle": "Ouvrir le site {{slug}} ?",
|
|
85
|
+
"closeTitle": "Fermer le site {{slug}} ?",
|
|
86
|
+
"openText": "La boutique redevient visible par tous (en moins d'une minute).",
|
|
87
|
+
"closeText": "Les visiteurs verront « Boutique fermée ». Les clients en train de payer peuvent finir leur commande ; le compte et le suivi de commande restent accessibles. Les commandes ne sont pas touchées.",
|
|
88
|
+
"opened": "Site ouvert.",
|
|
89
|
+
"closed": "Site fermé."
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"reviews": {
|
|
93
|
+
"nav": "Avis",
|
|
94
|
+
"title": "Avis clients",
|
|
95
|
+
"description": "Avis laissés par les acheteurs (lien de l’e-mail « commande livrée »). Un avis n’apparaît sur les boutiques qu’après validation.",
|
|
96
|
+
"allBoutiques": "Toutes les boutiques",
|
|
97
|
+
"status": {
|
|
98
|
+
"pending": "En attente",
|
|
99
|
+
"approved": "Validés",
|
|
100
|
+
"rejected": "Refusés",
|
|
101
|
+
"all": "Tous"
|
|
102
|
+
},
|
|
103
|
+
"col": {
|
|
104
|
+
"product": "Produit · boutique",
|
|
105
|
+
"review": "Avis",
|
|
106
|
+
"author": "Client",
|
|
107
|
+
"status": "Statut",
|
|
108
|
+
"actions": "Actions"
|
|
109
|
+
},
|
|
110
|
+
"approve": "Valider",
|
|
111
|
+
"reject": "Refuser",
|
|
112
|
+
"delete": "Supprimer",
|
|
113
|
+
"cancel": "Annuler",
|
|
114
|
+
"deleteTitle": "Supprimer cet avis ?",
|
|
115
|
+
"deleteText": "L’avis de {{name}} sera supprimé définitivement des boutiques.",
|
|
116
|
+
"deletedProduct": "Produit supprimé",
|
|
117
|
+
"empty": "Aucun avis pour ce filtre.",
|
|
118
|
+
"emptyPending": "Aucun avis en attente : tout est à jour.",
|
|
119
|
+
"range": "{{from}}–{{to}} sur {{count}}",
|
|
120
|
+
"prev": "Précédent",
|
|
121
|
+
"next": "Suivant",
|
|
122
|
+
"toast": {
|
|
123
|
+
"approved": "Avis validé : il est visible sur les boutiques",
|
|
124
|
+
"rejected": "Avis refusé : il n’apparaît pas",
|
|
125
|
+
"pending": "Avis remis en attente",
|
|
126
|
+
"deleted": "Avis supprimé"
|
|
127
|
+
}
|
|
70
128
|
}
|
|
71
129
|
}
|
|
@@ -10,6 +10,7 @@ type Texts = {
|
|
|
10
10
|
topbar?: string[]
|
|
11
11
|
hero?: Partial<Record<"eyebrow" | "title" | "highlight" | "sub" | "cta" | "cta2", string>> & { bubble?: string[]; image?: string }
|
|
12
12
|
promo?: Partial<Record<"eyebrow" | "title" | "text" | "cta" | "seal" | "image", string>>
|
|
13
|
+
seo?: Partial<Record<"title" | "description" | "image", string>>
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export type Campaign = Texts & {
|
|
@@ -25,6 +26,8 @@ export type Boutique = {
|
|
|
25
26
|
sales_channel_id: string
|
|
26
27
|
slug: string
|
|
27
28
|
description?: string | null
|
|
29
|
+
/** Mode multi : site ouvert (défaut) ou fermé */
|
|
30
|
+
online?: boolean
|
|
28
31
|
campaign: Campaign | null
|
|
29
32
|
}
|
|
30
33
|
|
|
@@ -44,14 +47,22 @@ const clean = <T extends Record<string, unknown>>(o: T): Partial<T> | undefined
|
|
|
44
47
|
return Object.keys(out).length ? out : undefined
|
|
45
48
|
}
|
|
46
49
|
|
|
47
|
-
|
|
50
|
+
// Texte identique à celui du thème = non enregistré : la boutique suit le thème (et ses mises à jour).
|
|
51
|
+
const same = (a: string, b?: string) => a.trim() === (b ?? "").trim()
|
|
52
|
+
const ifChanged = <T extends Record<string, string>>(o: T, def: Partial<Record<keyof T, string>> = {}) =>
|
|
53
|
+
Object.fromEntries(Object.entries(o).map(([k, v]) => [k, same(v, def[k]) ? "" : v])) as T
|
|
54
|
+
const listChanged = (list: string[], def: string[] = []) => (list.every((x, i) => same(x, def[i])) ? [] : list)
|
|
55
|
+
|
|
56
|
+
/** Panneau d'édition de la campagne d'une boutique. Champs pré-remplis avec les textes du thème (defaults). */
|
|
48
57
|
export function CampaignDrawer({
|
|
49
58
|
boutique,
|
|
59
|
+
defaults,
|
|
50
60
|
others,
|
|
51
61
|
queryKey,
|
|
52
62
|
onClose,
|
|
53
63
|
}: {
|
|
54
64
|
boutique: Boutique
|
|
65
|
+
defaults?: Texts | null
|
|
55
66
|
others: string[]
|
|
56
67
|
queryKey: string[]
|
|
57
68
|
onClose: () => void
|
|
@@ -59,27 +70,28 @@ export function CampaignDrawer({
|
|
|
59
70
|
const { t } = useTranslation()
|
|
60
71
|
const queryClient = useQueryClient()
|
|
61
72
|
const c = boutique.campaign
|
|
73
|
+
const def: Texts = defaults ?? {}
|
|
62
74
|
|
|
63
75
|
const [startsAt, setStartsAt] = useState(toLocal(c?.startsAt))
|
|
64
76
|
const [endsAt, setEndsAt] = useState(toLocal(c?.endsAt))
|
|
65
77
|
const [nextCampaign, setNextCampaign] = useState(c?.nextCampaign ?? "")
|
|
66
78
|
const [vars, setVars] = useState({ discount: c?.vars?.discount ?? "", freeShipping: c?.vars?.freeShipping ?? "" })
|
|
67
|
-
const [topbar, setTopbar] = useState<string[]>([0, 1, 2].map((i) => c?.topbar?.[i] ?? ""))
|
|
79
|
+
const [topbar, setTopbar] = useState<string[]>([0, 1, 2].map((i) => (c?.topbar?.length ? c.topbar[i] : def.topbar?.[i]) ?? ""))
|
|
68
80
|
const [hero, setHero] = useState({
|
|
69
|
-
eyebrow: c?.hero?.eyebrow ?? "",
|
|
70
|
-
title: c?.hero?.title ?? "",
|
|
71
|
-
highlight: c?.hero?.highlight ?? "",
|
|
72
|
-
sub: c?.hero?.sub ?? "",
|
|
73
|
-
cta: c?.hero?.cta ?? "",
|
|
74
|
-
cta2: c?.hero?.cta2 ?? "",
|
|
81
|
+
eyebrow: c?.hero?.eyebrow ?? def.hero?.eyebrow ?? "",
|
|
82
|
+
title: c?.hero?.title ?? def.hero?.title ?? "",
|
|
83
|
+
highlight: c?.hero?.highlight ?? def.hero?.highlight ?? "",
|
|
84
|
+
sub: c?.hero?.sub ?? def.hero?.sub ?? "",
|
|
85
|
+
cta: c?.hero?.cta ?? def.hero?.cta ?? "",
|
|
86
|
+
cta2: c?.hero?.cta2 ?? def.hero?.cta2 ?? "",
|
|
75
87
|
})
|
|
76
|
-
const [bubble, setBubble] = useState<string[]>([0, 1, 2].map((i) => c?.hero?.bubble?.[i] ?? ""))
|
|
88
|
+
const [bubble, setBubble] = useState<string[]>([0, 1, 2].map((i) => (c?.hero?.bubble?.length ? c.hero.bubble[i] : def.hero?.bubble?.[i]) ?? ""))
|
|
77
89
|
const [promo, setPromo] = useState({
|
|
78
|
-
eyebrow: c?.promo?.eyebrow ?? "",
|
|
79
|
-
title: c?.promo?.title ?? "",
|
|
80
|
-
text: c?.promo?.text ?? "",
|
|
81
|
-
cta: c?.promo?.cta ?? "",
|
|
82
|
-
seal: c?.promo?.seal ?? "",
|
|
90
|
+
eyebrow: c?.promo?.eyebrow ?? def.promo?.eyebrow ?? "",
|
|
91
|
+
title: c?.promo?.title ?? def.promo?.title ?? "",
|
|
92
|
+
text: c?.promo?.text ?? def.promo?.text ?? "",
|
|
93
|
+
cta: c?.promo?.cta ?? def.promo?.cta ?? "",
|
|
94
|
+
seal: c?.promo?.seal ?? def.promo?.seal ?? "",
|
|
83
95
|
})
|
|
84
96
|
const [heroImage, setHeroImage] = useState(c?.hero?.image ?? "")
|
|
85
97
|
const [promoImage, setPromoImage] = useState(c?.promo?.image ?? "")
|
|
@@ -124,9 +136,9 @@ export function CampaignDrawer({
|
|
|
124
136
|
nextCampaign: nextCampaign || null,
|
|
125
137
|
vars: clean(vars) ?? null,
|
|
126
138
|
...media(),
|
|
127
|
-
topbar: topbar.some((m) => m.trim()) ? topbar.filter((m) => m.trim()) : undefined,
|
|
128
|
-
hero: clean({ ...hero, bubble: bubble.some((b) => b.trim()) ? bubble : undefined }),
|
|
129
|
-
promo: clean(promo),
|
|
139
|
+
topbar: listChanged(topbar, def.topbar).some((m) => m.trim()) ? topbar.filter((m) => m.trim()) : undefined,
|
|
140
|
+
hero: clean({ ...ifChanged(hero, def.hero), bubble: listChanged(bubble, def.hero?.bubble).some((b) => b.trim()) ? bubble : undefined }),
|
|
141
|
+
promo: clean(ifChanged(promo, def.promo)),
|
|
130
142
|
seo: clean({ ...seo, image: seoImage || undefined }),
|
|
131
143
|
})
|
|
132
144
|
}
|