@salifbiaye/create-boutique 0.6.0 → 0.7.1

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.
Files changed (182) hide show
  1. package/cli/dist/code.js +110 -0
  2. package/cli/dist/commands.js +145 -11
  3. package/cli/dist/create.js +54 -7
  4. package/cli/dist/generate.js +65 -4
  5. package/cli/dist/guide.js +10 -2
  6. package/cli/dist/index.js +20 -7
  7. package/cli/dist/payments.js +150 -0
  8. package/docs/fiche-client.en.html +2 -2
  9. package/docs/fiche-client.html +2 -2
  10. package/docs/guide-complet.fr.html +681 -256
  11. package/docs/guide-images/categories.jpg +0 -0
  12. package/docs/guide-images/clients.jpg +0 -0
  13. package/docs/guide-images/collections.jpg +0 -0
  14. package/docs/guide-images/commande-detail.jpg +0 -0
  15. package/docs/guide-images/commande-expedier.jpg +0 -0
  16. package/docs/guide-images/commande-menu.jpg +0 -0
  17. package/docs/guide-images/commande-paiement.jpg +0 -0
  18. package/docs/guide-images/commandes-liste.jpg +0 -0
  19. package/docs/guide-images/connexion.jpg +0 -0
  20. package/docs/guide-images/groupes-clients.jpg +0 -0
  21. package/docs/guide-images/inventaire.jpg +0 -0
  22. package/docs/guide-images/listes-prix.jpg +0 -0
  23. package/docs/guide-images/menu.jpg +0 -0
  24. package/docs/guide-images/mes-boutiques-fermer.jpg +0 -0
  25. package/docs/guide-images/mes-boutiques-panneau.jpg +0 -0
  26. package/docs/guide-images/mes-boutiques.jpg +0 -0
  27. package/docs/guide-images/parametres-boutique.jpg +0 -0
  28. package/docs/guide-images/parametres-canaux.jpg +0 -0
  29. package/docs/guide-images/parametres-emplacements.jpg +0 -0
  30. package/docs/guide-images/parametres-profil.jpg +0 -0
  31. package/docs/guide-images/parametres-regions.jpg +0 -0
  32. package/docs/guide-images/parametres-utilisateurs.jpg +0 -0
  33. package/docs/guide-images/produit-creer-details.jpg +0 -0
  34. package/docs/guide-images/produit-detail.jpg +0 -0
  35. package/docs/guide-images/produit-variantes-menu.jpg +0 -0
  36. package/docs/guide-images/produit-variantes.jpg +0 -0
  37. package/docs/guide-images/produits-liste.jpg +0 -0
  38. package/docs/guide-images/promotion-details.jpg +0 -0
  39. package/docs/guide-images/promotion-type.jpg +0 -0
  40. package/docs/guide-images/promotions-liste.jpg +0 -0
  41. package/docs/guide-images/site-boutique.jpg +0 -0
  42. package/docs/guide-images/site-fermee.png +0 -0
  43. package/docs/guide-images/vue-ensemble.jpg +0 -0
  44. package/package.json +3 -2
  45. package/templates/backend/medusa-config.ts +31 -25
  46. package/templates/backend/package-lock.json +21 -0
  47. package/templates/backend/package.json +2 -0
  48. package/templates/backend/src/admin/i18n/json/en.json +51 -3
  49. package/templates/backend/src/admin/i18n/json/fr.json +51 -3
  50. package/templates/backend/src/admin/routes/boutique-campaigns/campaign-drawer.tsx +2 -0
  51. package/templates/backend/src/admin/routes/boutique-campaigns/page.tsx +36 -1
  52. package/templates/backend/src/admin/routes/reviews/page.tsx +259 -0
  53. package/templates/backend/src/api/admin/boutique-campaigns/route.ts +3 -1
  54. package/templates/backend/src/api/admin/boutique-online/[salesChannelId]/route.ts +22 -0
  55. package/templates/backend/src/api/admin/reviews/[id]/route.ts +15 -0
  56. package/templates/backend/src/api/admin/reviews/route.ts +47 -0
  57. package/templates/backend/src/api/middlewares.ts +7 -0
  58. package/templates/backend/src/api/store/campaign/route.ts +5 -1
  59. package/templates/backend/src/api/store/reviews/latest/route.ts +39 -0
  60. package/templates/backend/src/api/store/reviews/order/route.ts +36 -0
  61. package/templates/backend/src/api/store/reviews/route.ts +43 -0
  62. package/templates/backend/src/api/store/reviews/summary/route.ts +28 -0
  63. package/templates/backend/src/emails/en.json +3 -1
  64. package/templates/backend/src/emails/fr.json +3 -1
  65. package/templates/backend/src/lib/email-config.ts +14 -0
  66. package/templates/backend/src/lib/emails.ts +9 -0
  67. package/templates/backend/src/lib/notify.ts +14 -8
  68. package/templates/backend/src/lib/payment-config.ts +66 -0
  69. package/templates/backend/src/lib/project.ts +12 -0
  70. package/templates/backend/src/lib/redirect-payment.ts +264 -0
  71. package/templates/backend/src/lib/review-token.ts +40 -0
  72. package/templates/backend/src/lib/reviews.ts +25 -0
  73. package/templates/backend/src/modules/naboopay/index.ts +6 -0
  74. package/templates/backend/src/modules/naboopay/service.ts +92 -0
  75. package/templates/backend/src/modules/paydunya/index.ts +6 -0
  76. package/templates/backend/src/modules/paydunya/service.ts +89 -0
  77. package/templates/backend/src/modules/paytech/index.ts +6 -0
  78. package/templates/backend/src/modules/paytech/service.ts +114 -0
  79. package/templates/backend/src/modules/product-review/index.ts +8 -0
  80. package/templates/backend/src/modules/product-review/migrations/Migration20261001090000.ts +18 -0
  81. package/templates/backend/src/modules/product-review/models/review.ts +25 -0
  82. package/templates/backend/src/modules/product-review/service.ts +6 -0
  83. package/templates/backend/src/modules/product-review/validators.ts +27 -0
  84. package/templates/backend/src/modules/smtp/index.ts +6 -0
  85. package/templates/backend/src/modules/smtp/service.ts +73 -0
  86. package/templates/backend/src/scripts/seed.ts +52 -36
  87. package/templates/backend/src/subscribers/fulfillment-emails.ts +1 -1
  88. package/templates/backend/src/workflows/reviews.ts +21 -0
  89. package/templates/backend/src/workflows/steps/create-review.ts +52 -0
  90. package/templates/backend/src/workflows/steps/update-review.ts +36 -0
  91. package/templates/storefront/PERSONNALISER.md +134 -0
  92. package/templates/storefront/locales/en.json +198 -11
  93. package/templates/storefront/locales/fr.json +198 -11
  94. package/templates/storefront/public/custom/LISEZMOI.txt +2 -0
  95. package/templates/storefront/src/app/(checkout)/layout.tsx +1 -1
  96. package/templates/storefront/src/app/(main)/layout.tsx +8 -1
  97. package/templates/storefront/src/app/(main)/legal/[page]/page.tsx +42 -7
  98. package/templates/storefront/src/app/(main)/page.tsx +149 -4
  99. package/templates/storefront/src/app/(main)/review/page.tsx +60 -0
  100. package/templates/storefront/src/app/(main)/store/page.tsx +3 -1
  101. package/templates/storefront/src/app/(main)/template.tsx +17 -2
  102. package/templates/storefront/src/app/layout.tsx +13 -1
  103. package/templates/storefront/src/lib/boutique/campaign.ts +5 -1
  104. package/templates/storefront/src/lib/boutique/page-name.ts +25 -0
  105. package/templates/storefront/src/lib/boutique/schema.ts +3 -1
  106. package/templates/storefront/src/lib/constants.tsx +17 -0
  107. package/templates/storefront/src/lib/data/review-actions.ts +28 -0
  108. package/templates/storefront/src/lib/data/reviews.ts +64 -0
  109. package/templates/storefront/src/modules/checkout/components/payment/index.tsx +26 -4
  110. package/templates/storefront/src/modules/checkout/components/payment-button/index.tsx +85 -2
  111. package/templates/storefront/src/modules/products/templates/index.tsx +5 -0
  112. package/templates/storefront/src/modules/store/components/shop-search.tsx +85 -0
  113. package/templates/storefront/src/modules/store/templates/index.tsx +16 -8
  114. package/templates/storefront/src/modules/store/templates/paginated-products.tsx +15 -3
  115. package/templates/storefront/src/modules/ub/album.tsx +2 -0
  116. package/templates/storefront/src/modules/ub/arena.tsx +182 -0
  117. package/templates/storefront/src/modules/ub/campaign-blocks.tsx +3 -2
  118. package/templates/storefront/src/modules/ub/campaign-ended.tsx +27 -13
  119. package/templates/storefront/src/modules/ub/cinema.tsx +2 -0
  120. package/templates/storefront/src/modules/ub/cookie-banner.tsx +62 -0
  121. package/templates/storefront/src/modules/ub/gala.tsx +2 -0
  122. package/templates/storefront/src/modules/ub/grimoire.tsx +35 -8
  123. package/templates/storefront/src/modules/ub/harvest.tsx +2 -1
  124. package/templates/storefront/src/modules/ub/hero-image.tsx +14 -0
  125. package/templates/storefront/src/modules/ub/home-kit.ts +45 -0
  126. package/templates/storefront/src/modules/ub/lantern.tsx +244 -0
  127. package/templates/storefront/src/modules/ub/locker.tsx +180 -0
  128. package/templates/storefront/src/modules/ub/mosaic.tsx +189 -0
  129. package/templates/storefront/src/modules/ub/nav.tsx +8 -5
  130. package/templates/storefront/src/modules/ub/review-form.tsx +62 -0
  131. package/templates/storefront/src/modules/ub/reviews.tsx +131 -0
  132. package/templates/storefront/src/modules/ub/riviera.tsx +175 -0
  133. package/templates/storefront/src/modules/ub/souk.tsx +214 -0
  134. package/templates/storefront/src/modules/ub/sunset-checklist.tsx +95 -0
  135. package/templates/storefront/src/modules/ub/sunset.tsx +164 -0
  136. package/templates/storefront/src/modules/ub/tech.tsx +2 -2
  137. package/templates/storefront/src/modules/ub/training.tsx +196 -0
  138. package/templates/storefront/src/proxy.ts +2 -0
  139. package/templates/storefront/src/styles/custom.css +26 -0
  140. package/templates/storefront/src/styles/ub/album.css +1 -1
  141. package/templates/storefront/src/styles/ub/arena.css +120 -0
  142. package/templates/storefront/src/styles/ub/cinema.css +1 -1
  143. package/templates/storefront/src/styles/ub/gala.css +1 -1
  144. package/templates/storefront/src/styles/ub/grimoire.css +23 -7
  145. package/templates/storefront/src/styles/ub/harvest.css +7 -6
  146. package/templates/storefront/src/styles/ub/lantern.css +157 -0
  147. package/templates/storefront/src/styles/ub/locker.css +115 -0
  148. package/templates/storefront/src/styles/ub/media.css +10 -0
  149. package/templates/storefront/src/styles/ub/mosaic.css +118 -0
  150. package/templates/storefront/src/styles/ub/motion.css +0 -3
  151. package/templates/storefront/src/styles/ub/overrides.css +10 -0
  152. package/templates/storefront/src/styles/ub/pages.css +176 -0
  153. package/templates/storefront/src/styles/ub/reviews.css +85 -0
  154. package/templates/storefront/src/styles/ub/riviera.css +103 -0
  155. package/templates/storefront/src/styles/ub/souk.css +144 -0
  156. package/templates/storefront/src/styles/ub/sunset.css +123 -0
  157. package/templates/storefront/src/styles/ub/tech.css +2 -5
  158. package/templates/storefront/src/styles/ub/training.css +120 -0
  159. package/templates/storefront/themes/jeux/campaign.en.json +31 -0
  160. package/templates/storefront/themes/jeux/campaign.fr.json +31 -0
  161. package/templates/storefront/themes/jeux/theme.json +11 -0
  162. package/templates/storefront/themes/jeux-2/campaign.en.json +31 -0
  163. package/templates/storefront/themes/jeux-2/campaign.fr.json +31 -0
  164. package/templates/storefront/themes/jeux-2/theme.json +11 -0
  165. package/templates/storefront/themes/mubarak/campaign.en.json +31 -0
  166. package/templates/storefront/themes/mubarak/campaign.fr.json +31 -0
  167. package/templates/storefront/themes/mubarak/theme.json +11 -0
  168. package/templates/storefront/themes/mubarak-2/campaign.en.json +31 -0
  169. package/templates/storefront/themes/mubarak-2/campaign.fr.json +31 -0
  170. package/templates/storefront/themes/mubarak-2/theme.json +11 -0
  171. package/templates/storefront/themes/sport/campaign.en.json +31 -0
  172. package/templates/storefront/themes/sport/campaign.fr.json +31 -0
  173. package/templates/storefront/themes/sport/theme.json +11 -0
  174. package/templates/storefront/themes/sport-2/campaign.en.json +31 -0
  175. package/templates/storefront/themes/sport-2/campaign.fr.json +31 -0
  176. package/templates/storefront/themes/sport-2/theme.json +11 -0
  177. package/templates/storefront/themes/summer/campaign.en.json +31 -0
  178. package/templates/storefront/themes/summer/campaign.fr.json +31 -0
  179. package/templates/storefront/themes/summer/theme.json +11 -0
  180. package/templates/storefront/themes/summer-2/campaign.en.json +31 -0
  181. package/templates/storefront/themes/summer-2/campaign.fr.json +31 -0
  182. package/templates/storefront/themes/summer-2/theme.json +11 -0
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.6.0",
3
+ "version": "0.7.1",
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 seule clé Resend pour tous les projets (mode plateforme).
67
- // Sans clé : aucun e-mail envoyé, la boutique fonctionne quand même (voir src/lib/notify.ts).
68
- if (process.env.RESEND_API_KEY) {
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
- resolve: "./src/modules/resend",
75
- id: "resend",
76
- options: { channels: ["email"], api_key: process.env.RESEND_API_KEY, from: process.env.EMAIL_FROM },
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": "Campaigns",
4
- "title": "Campaigns",
5
- "description": "Texts, dates and hero image of each of your stores. Changes appear on the store within a minute.",
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",
@@ -76,6 +76,54 @@
76
76
  "confirmText": "The site will switch from {{from}} to {{to}}. Customers paying right now can finish their order.",
77
77
  "confirm": "Put live",
78
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"
79
127
  }
80
128
  }
81
129
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "campaigns": {
3
- "nav": "Campagnes",
4
- "title": "Campagnes",
5
- "description": "Textes, dates et visuel de chacune de vos boutiques. Les modifications apparaissent sur la boutique en une minute.",
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",
@@ -76,6 +76,54 @@
76
76
  "confirmText": "Le site va passer de {{from}} à {{to}}. Les clients qui paient en ce moment peuvent finir leur commande.",
77
77
  "confirm": "Mettre en ligne",
78
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é"
79
127
  }
80
128
  }
81
129
  }
@@ -26,6 +26,8 @@ export type Boutique = {
26
26
  sales_channel_id: string
27
27
  slug: string
28
28
  description?: string | null
29
+ /** Mode multi : site ouvert (défaut) ou fermé */
30
+ online?: boolean
29
31
  campaign: Campaign | null
30
32
  }
31
33
 
@@ -54,6 +54,29 @@ const CampaignsPage = () => {
54
54
  sdk.client.fetch<{ mode?: "multi" | "single"; active?: string | null; boutiques: Boutique[] }>("/admin/boutique-campaigns"),
55
55
  })
56
56
  const single = data?.mode === "single"
57
+ const multi = !!data && !single
58
+
59
+ // Mode multi : ouvrir / fermer le site d'une boutique (manuel, indépendant des dates de campagne)
60
+ const setOnline = useMutation({
61
+ mutationFn: ({ id, online }: { id: string; online: boolean }) =>
62
+ sdk.client.fetch(`/admin/boutique-online/${id}`, { method: "POST", body: { online } }),
63
+ onSuccess: (_d, v) => {
64
+ queryClient.invalidateQueries({ queryKey: QUERY_KEY })
65
+ toast.success(t(v.online ? "campaigns.site.opened" : "campaigns.site.closed"))
66
+ },
67
+ onError: (e: Error) => toast.error(e.message),
68
+ })
69
+ const toggleSite = async (b: Boutique) => {
70
+ const online = b.online === false
71
+ const ok = await prompt({
72
+ title: t(online ? "campaigns.site.openTitle" : "campaigns.site.closeTitle", { slug: b.slug }),
73
+ description: t(online ? "campaigns.site.openText" : "campaigns.site.closeText"),
74
+ confirmText: t(online ? "campaigns.site.open" : "campaigns.site.close"),
75
+ cancelText: t("campaigns.cancel"),
76
+ variant: online ? "confirmation" : "danger",
77
+ })
78
+ if (ok) setOnline.mutate({ id: b.sales_channel_id, online })
79
+ }
57
80
 
58
81
  // Mode single : met une boutique en ligne (la précédente est retirée ; il y en a toujours exactement une)
59
82
  const activate = useMutation({
@@ -102,7 +125,7 @@ const CampaignsPage = () => {
102
125
  <Table.HeaderCell>{t("campaigns.store")}</Table.HeaderCell>
103
126
  <Table.HeaderCell>{t("campaigns.dates")}</Table.HeaderCell>
104
127
  <Table.HeaderCell>{t("campaigns.status")}</Table.HeaderCell>
105
- {single && <Table.HeaderCell>{t("campaigns.online.column")}</Table.HeaderCell>}
128
+ {(single || multi) && <Table.HeaderCell>{t("campaigns.online.column")}</Table.HeaderCell>}
106
129
  <Table.HeaderCell />
107
130
  </Table.Row>
108
131
  </Table.Header>
@@ -144,6 +167,18 @@ const CampaignsPage = () => {
144
167
  )}
145
168
  </Table.Cell>
146
169
  )}
170
+ {multi && (
171
+ <Table.Cell>
172
+ <div className="flex items-center gap-x-2">
173
+ <Badge size="2xsmall" color={b.online === false ? "grey" : "green"}>
174
+ {t(b.online === false ? "campaigns.site.offline" : "campaigns.online.live")}
175
+ </Badge>
176
+ <Button size="small" variant="transparent" onClick={() => toggleSite(b)} disabled={setOnline.isPending}>
177
+ {t(b.online === false ? "campaigns.site.open" : "campaigns.site.close")}
178
+ </Button>
179
+ </div>
180
+ </Table.Cell>
181
+ )}
147
182
  <Table.Cell className="text-right">
148
183
  <Button size="small" variant="secondary" onClick={() => setEditing(b)}>
149
184
  <PencilSquare />