@salifbiaye/create-boutique 0.1.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 (316) hide show
  1. package/README.md +1 -0
  2. package/cli/dist/commands.js +382 -0
  3. package/cli/dist/create.js +194 -0
  4. package/cli/dist/docker.js +19 -0
  5. package/cli/dist/generate.js +322 -0
  6. package/cli/dist/guide.js +72 -0
  7. package/cli/dist/index.js +156 -0
  8. package/cli/dist/paths.js +29 -0
  9. package/cli/dist/ports.js +54 -0
  10. package/cli/dist/project.js +119 -0
  11. package/cli/dist/themes.js +24 -0
  12. package/docs/fiche-client.en.html +137 -0
  13. package/docs/fiche-client.html +137 -0
  14. package/docs/guide-complet.en.html +259 -0
  15. package/docs/guide-complet.fr.html +259 -0
  16. package/package.json +51 -0
  17. package/templates/backend/.dockerignore +9 -0
  18. package/templates/backend/Dockerfile +30 -0
  19. package/templates/backend/README.md +78 -0
  20. package/templates/backend/TEMPLATE_VERSION +1 -0
  21. package/templates/backend/docker/entrypoint.sh +28 -0
  22. package/templates/backend/instrumentation.ts +24 -0
  23. package/templates/backend/medusa-config.ts +114 -0
  24. package/templates/backend/package-lock.json +16222 -0
  25. package/templates/backend/package.json +39 -0
  26. package/templates/backend/src/admin/README.md +33 -0
  27. package/templates/backend/src/admin/components/image-field.tsx +61 -0
  28. package/templates/backend/src/admin/i18n/README.md +58 -0
  29. package/templates/backend/src/admin/i18n/index.ts +8 -0
  30. package/templates/backend/src/admin/i18n/json/en.json +66 -0
  31. package/templates/backend/src/admin/i18n/json/fr.json +66 -0
  32. package/templates/backend/src/admin/lib/client.ts +9 -0
  33. package/templates/backend/src/admin/routes/boutique-campaigns/campaign-drawer.tsx +286 -0
  34. package/templates/backend/src/admin/routes/boutique-campaigns/page.tsx +113 -0
  35. package/templates/backend/src/admin/tsconfig.json +24 -0
  36. package/templates/backend/src/admin/vite-env.d.ts +1 -0
  37. package/templates/backend/src/admin/widgets/category-image.tsx +56 -0
  38. package/templates/backend/src/api/README.md +135 -0
  39. package/templates/backend/src/api/admin/boutique-campaigns/[salesChannelId]/route.ts +30 -0
  40. package/templates/backend/src/api/admin/boutique-campaigns/route.ts +24 -0
  41. package/templates/backend/src/api/middlewares.ts +28 -0
  42. package/templates/backend/src/api/rate-limit.ts +30 -0
  43. package/templates/backend/src/api/route.ts +6 -0
  44. package/templates/backend/src/api/store/campaign/route.ts +25 -0
  45. package/templates/backend/src/api/store/order-lookup/route.ts +74 -0
  46. package/templates/backend/src/emails/en.json +64 -0
  47. package/templates/backend/src/emails/fr.json +64 -0
  48. package/templates/backend/src/jobs/README.md +38 -0
  49. package/templates/backend/src/lib/emails.ts +94 -0
  50. package/templates/backend/src/lib/notify.ts +148 -0
  51. package/templates/backend/src/lib/project.ts +146 -0
  52. package/templates/backend/src/links/README.md +26 -0
  53. package/templates/backend/src/links/campaign-sales-channel.ts +9 -0
  54. package/templates/backend/src/modules/README.md +117 -0
  55. package/templates/backend/src/modules/campaign/index.ts +9 -0
  56. package/templates/backend/src/modules/campaign/migrations/.snapshot-boutique-campaign.json +272 -0
  57. package/templates/backend/src/modules/campaign/migrations/Migration20260928141440.ts +16 -0
  58. package/templates/backend/src/modules/campaign/migrations/Migration20260928145243.ts +13 -0
  59. package/templates/backend/src/modules/campaign/models/campaign.ts +28 -0
  60. package/templates/backend/src/modules/campaign/service.ts +6 -0
  61. package/templates/backend/src/modules/campaign/validators.ts +94 -0
  62. package/templates/backend/src/modules/resend/index.ts +6 -0
  63. package/templates/backend/src/modules/resend/service.ts +63 -0
  64. package/templates/backend/src/scripts/README.md +63 -0
  65. package/templates/backend/src/scripts/campaign-export.ts +15 -0
  66. package/templates/backend/src/scripts/campaign-import.ts +38 -0
  67. package/templates/backend/src/scripts/price-report.ts +34 -0
  68. package/templates/backend/src/scripts/seed.ts +418 -0
  69. package/templates/backend/src/scripts/storage.ts +63 -0
  70. package/templates/backend/src/subscribers/README.md +61 -0
  71. package/templates/backend/src/subscribers/account-emails.ts +27 -0
  72. package/templates/backend/src/subscribers/fulfillment-emails.ts +42 -0
  73. package/templates/backend/src/subscribers/order-emails.ts +12 -0
  74. package/templates/backend/src/subscribers/return-refund-emails.ts +50 -0
  75. package/templates/backend/src/workflows/README.md +81 -0
  76. package/templates/backend/src/workflows/steps/upsert-campaign.ts +53 -0
  77. package/templates/backend/src/workflows/upsert-campaign.ts +30 -0
  78. package/templates/backend/tsconfig.json +36 -0
  79. package/templates/storefront/.dockerignore +7 -0
  80. package/templates/storefront/.github/ISSUE_TEMPLATE/bug-report.yml +71 -0
  81. package/templates/storefront/.prettierrc +8 -0
  82. package/templates/storefront/Dockerfile +37 -0
  83. package/templates/storefront/LICENSE +21 -0
  84. package/templates/storefront/README.md +127 -0
  85. package/templates/storefront/locales/en.json +269 -0
  86. package/templates/storefront/locales/fr.json +269 -0
  87. package/templates/storefront/next-env.d.ts +7 -0
  88. package/templates/storefront/next-sitemap.js +19 -0
  89. package/templates/storefront/next.config.js +29 -0
  90. package/templates/storefront/package-lock.json +9031 -0
  91. package/templates/storefront/package.json +49 -0
  92. package/templates/storefront/postcss.config.js +6 -0
  93. package/templates/storefront/public/favicon.ico +0 -0
  94. package/templates/storefront/scripts/sync-design.mjs +30 -0
  95. package/templates/storefront/src/app/(checkout)/checkout/page.tsx +31 -0
  96. package/templates/storefront/src/app/(checkout)/layout.tsx +29 -0
  97. package/templates/storefront/src/app/(checkout)/not-found.tsx +20 -0
  98. package/templates/storefront/src/app/(main)/account/@dashboard/addresses/page.tsx +34 -0
  99. package/templates/storefront/src/app/(main)/account/@dashboard/loading.tsx +9 -0
  100. package/templates/storefront/src/app/(main)/account/@dashboard/orders/details/[id]/page.tsx +33 -0
  101. package/templates/storefront/src/app/(main)/account/@dashboard/orders/page.tsx +37 -0
  102. package/templates/storefront/src/app/(main)/account/@dashboard/page.tsx +22 -0
  103. package/templates/storefront/src/app/(main)/account/@dashboard/profile/page.tsx +53 -0
  104. package/templates/storefront/src/app/(main)/account/@login/page.tsx +12 -0
  105. package/templates/storefront/src/app/(main)/account/layout.tsx +20 -0
  106. package/templates/storefront/src/app/(main)/account/loading.tsx +9 -0
  107. package/templates/storefront/src/app/(main)/cart/loading.tsx +5 -0
  108. package/templates/storefront/src/app/(main)/cart/not-found.tsx +21 -0
  109. package/templates/storefront/src/app/(main)/cart/page.tsx +21 -0
  110. package/templates/storefront/src/app/(main)/categories/[...category]/page.tsx +55 -0
  111. package/templates/storefront/src/app/(main)/collections/[handle]/page.tsx +55 -0
  112. package/templates/storefront/src/app/(main)/layout.tsx +43 -0
  113. package/templates/storefront/src/app/(main)/legal/[page]/page.tsx +42 -0
  114. package/templates/storefront/src/app/(main)/not-found.tsx +21 -0
  115. package/templates/storefront/src/app/(main)/order/[id]/confirmed/loading.tsx +5 -0
  116. package/templates/storefront/src/app/(main)/order/[id]/confirmed/page.tsx +23 -0
  117. package/templates/storefront/src/app/(main)/order/[id]/transfer/[token]/accept/page.tsx +42 -0
  118. package/templates/storefront/src/app/(main)/order/[id]/transfer/[token]/decline/page.tsx +42 -0
  119. package/templates/storefront/src/app/(main)/order/[id]/transfer/[token]/page.tsx +35 -0
  120. package/templates/storefront/src/app/(main)/order/track/page.tsx +32 -0
  121. package/templates/storefront/src/app/(main)/page.tsx +68 -0
  122. package/templates/storefront/src/app/(main)/products/[handle]/page.tsx +86 -0
  123. package/templates/storefront/src/app/(main)/store/page.tsx +28 -0
  124. package/templates/storefront/src/app/api/health/route.ts +13 -0
  125. package/templates/storefront/src/app/layout.tsx +42 -0
  126. package/templates/storefront/src/app/not-found.tsx +21 -0
  127. package/templates/storefront/src/app/opengraph-image.jpg +0 -0
  128. package/templates/storefront/src/app/twitter-image.jpg +0 -0
  129. package/templates/storefront/src/instrumentation-node.ts +18 -0
  130. package/templates/storefront/src/instrumentation.ts +9 -0
  131. package/templates/storefront/src/lib/boutique/campaign.ts +116 -0
  132. package/templates/storefront/src/lib/boutique/config.ts +152 -0
  133. package/templates/storefront/src/lib/boutique/fonts.ts +29 -0
  134. package/templates/storefront/src/lib/boutique/i18n-client.tsx +34 -0
  135. package/templates/storefront/src/lib/boutique/i18n.ts +43 -0
  136. package/templates/storefront/src/lib/boutique/schema.ts +131 -0
  137. package/templates/storefront/src/lib/config.ts +39 -0
  138. package/templates/storefront/src/lib/constants.tsx +75 -0
  139. package/templates/storefront/src/lib/context/modal-context.tsx +34 -0
  140. package/templates/storefront/src/lib/data/cart.ts +434 -0
  141. package/templates/storefront/src/lib/data/categories.ts +49 -0
  142. package/templates/storefront/src/lib/data/collections.ts +59 -0
  143. package/templates/storefront/src/lib/data/cookies.ts +103 -0
  144. package/templates/storefront/src/lib/data/customer.ts +261 -0
  145. package/templates/storefront/src/lib/data/fulfillment.ts +68 -0
  146. package/templates/storefront/src/lib/data/onboarding.ts +9 -0
  147. package/templates/storefront/src/lib/data/order-lookup.ts +34 -0
  148. package/templates/storefront/src/lib/data/orders.ts +112 -0
  149. package/templates/storefront/src/lib/data/payment.ts +35 -0
  150. package/templates/storefront/src/lib/data/products.ts +127 -0
  151. package/templates/storefront/src/lib/data/regions.ts +70 -0
  152. package/templates/storefront/src/lib/data/variants.ts +38 -0
  153. package/templates/storefront/src/lib/hooks/use-in-view.tsx +29 -0
  154. package/templates/storefront/src/lib/hooks/use-toggle-state.tsx +46 -0
  155. package/templates/storefront/src/lib/util/compare-addresses.ts +28 -0
  156. package/templates/storefront/src/lib/util/get-percentage-diff.ts +6 -0
  157. package/templates/storefront/src/lib/util/get-product-price.ts +79 -0
  158. package/templates/storefront/src/lib/util/isEmpty.ts +11 -0
  159. package/templates/storefront/src/lib/util/medusa-error.ts +22 -0
  160. package/templates/storefront/src/lib/util/money.ts +35 -0
  161. package/templates/storefront/src/lib/util/product.ts +5 -0
  162. package/templates/storefront/src/lib/util/repeat.ts +5 -0
  163. package/templates/storefront/src/lib/util/sort-products.ts +50 -0
  164. package/templates/storefront/src/modules/account/components/account-info/index.tsx +140 -0
  165. package/templates/storefront/src/modules/account/components/account-nav/index.tsx +198 -0
  166. package/templates/storefront/src/modules/account/components/address-book/index.tsx +28 -0
  167. package/templates/storefront/src/modules/account/components/address-card/add-address.tsx +169 -0
  168. package/templates/storefront/src/modules/account/components/address-card/edit-address-modal.tsx +241 -0
  169. package/templates/storefront/src/modules/account/components/login/index.tsx +65 -0
  170. package/templates/storefront/src/modules/account/components/order-card/index.tsx +88 -0
  171. package/templates/storefront/src/modules/account/components/order-overview/index.tsx +47 -0
  172. package/templates/storefront/src/modules/account/components/overview/index.tsx +169 -0
  173. package/templates/storefront/src/modules/account/components/profile-billing-address/index.tsx +193 -0
  174. package/templates/storefront/src/modules/account/components/profile-email/index.tsx +75 -0
  175. package/templates/storefront/src/modules/account/components/profile-name/index.tsx +79 -0
  176. package/templates/storefront/src/modules/account/components/profile-password/index.tsx +72 -0
  177. package/templates/storefront/src/modules/account/components/profile-phone/index.tsx +74 -0
  178. package/templates/storefront/src/modules/account/components/register/index.tsx +107 -0
  179. package/templates/storefront/src/modules/account/components/transfer-request-form/index.tsx +83 -0
  180. package/templates/storefront/src/modules/account/templates/account-layout.tsx +43 -0
  181. package/templates/storefront/src/modules/account/templates/login-template.tsx +27 -0
  182. package/templates/storefront/src/modules/cart/components/cart-item-select/index.tsx +75 -0
  183. package/templates/storefront/src/modules/cart/components/empty-cart-message/index.tsx +25 -0
  184. package/templates/storefront/src/modules/cart/components/item/index.tsx +144 -0
  185. package/templates/storefront/src/modules/cart/components/sign-in-prompt/index.tsx +27 -0
  186. package/templates/storefront/src/modules/cart/templates/index.tsx +51 -0
  187. package/templates/storefront/src/modules/cart/templates/items.tsx +58 -0
  188. package/templates/storefront/src/modules/cart/templates/preview.tsx +51 -0
  189. package/templates/storefront/src/modules/cart/templates/summary.tsx +50 -0
  190. package/templates/storefront/src/modules/categories/templates/index.tsx +94 -0
  191. package/templates/storefront/src/modules/checkout/components/address-select/index.tsx +117 -0
  192. package/templates/storefront/src/modules/checkout/components/addresses/index.tsx +186 -0
  193. package/templates/storefront/src/modules/checkout/components/billing_address/index.tsx +114 -0
  194. package/templates/storefront/src/modules/checkout/components/country-select/index.tsx +50 -0
  195. package/templates/storefront/src/modules/checkout/components/discount-code/index.tsx +181 -0
  196. package/templates/storefront/src/modules/checkout/components/error-message/index.tsx +13 -0
  197. package/templates/storefront/src/modules/checkout/components/payment/index.tsx +261 -0
  198. package/templates/storefront/src/modules/checkout/components/payment-button/index.tsx +195 -0
  199. package/templates/storefront/src/modules/checkout/components/payment-container/index.tsx +130 -0
  200. package/templates/storefront/src/modules/checkout/components/payment-test/index.tsx +12 -0
  201. package/templates/storefront/src/modules/checkout/components/payment-wrapper/index.tsx +42 -0
  202. package/templates/storefront/src/modules/checkout/components/payment-wrapper/stripe-wrapper.tsx +54 -0
  203. package/templates/storefront/src/modules/checkout/components/review/index.tsx +54 -0
  204. package/templates/storefront/src/modules/checkout/components/shipping/index.tsx +417 -0
  205. package/templates/storefront/src/modules/checkout/components/shipping-address/index.tsx +219 -0
  206. package/templates/storefront/src/modules/checkout/components/submit-button/index.tsx +32 -0
  207. package/templates/storefront/src/modules/checkout/templates/checkout-form/index.tsx +38 -0
  208. package/templates/storefront/src/modules/checkout/templates/checkout-summary/index.tsx +31 -0
  209. package/templates/storefront/src/modules/collections/templates/index.tsx +44 -0
  210. package/templates/storefront/src/modules/common/components/cart-totals/index.tsx +84 -0
  211. package/templates/storefront/src/modules/common/components/checkbox/index.tsx +44 -0
  212. package/templates/storefront/src/modules/common/components/delete-button/index.tsx +42 -0
  213. package/templates/storefront/src/modules/common/components/divider/index.tsx +9 -0
  214. package/templates/storefront/src/modules/common/components/filter-radio-group/index.tsx +61 -0
  215. package/templates/storefront/src/modules/common/components/input/index.tsx +79 -0
  216. package/templates/storefront/src/modules/common/components/interactive-link/index.tsx +33 -0
  217. package/templates/storefront/src/modules/common/components/line-item-options/index.tsx +26 -0
  218. package/templates/storefront/src/modules/common/components/line-item-price/index.tsx +63 -0
  219. package/templates/storefront/src/modules/common/components/line-item-unit-price/index.tsx +62 -0
  220. package/templates/storefront/src/modules/common/components/localized-client-link/index.tsx +29 -0
  221. package/templates/storefront/src/modules/common/components/modal/index.tsx +118 -0
  222. package/templates/storefront/src/modules/common/components/native-select/index.tsx +76 -0
  223. package/templates/storefront/src/modules/common/components/radio/index.tsx +27 -0
  224. package/templates/storefront/src/modules/common/icons/back.tsx +37 -0
  225. package/templates/storefront/src/modules/common/icons/bancontact.tsx +26 -0
  226. package/templates/storefront/src/modules/common/icons/chevron-down.tsx +30 -0
  227. package/templates/storefront/src/modules/common/icons/eye-off.tsx +37 -0
  228. package/templates/storefront/src/modules/common/icons/eye.tsx +37 -0
  229. package/templates/storefront/src/modules/common/icons/fast-delivery.tsx +65 -0
  230. package/templates/storefront/src/modules/common/icons/ideal.tsx +26 -0
  231. package/templates/storefront/src/modules/common/icons/map-pin.tsx +37 -0
  232. package/templates/storefront/src/modules/common/icons/package.tsx +44 -0
  233. package/templates/storefront/src/modules/common/icons/paypal.tsx +30 -0
  234. package/templates/storefront/src/modules/common/icons/placeholder-image.tsx +44 -0
  235. package/templates/storefront/src/modules/common/icons/refresh.tsx +51 -0
  236. package/templates/storefront/src/modules/common/icons/spinner.tsx +37 -0
  237. package/templates/storefront/src/modules/common/icons/trash.tsx +51 -0
  238. package/templates/storefront/src/modules/common/icons/user.tsx +37 -0
  239. package/templates/storefront/src/modules/common/icons/x.tsx +37 -0
  240. package/templates/storefront/src/modules/layout/components/cart-mismatch-banner/index.tsx +59 -0
  241. package/templates/storefront/src/modules/order/components/help/index.tsx +26 -0
  242. package/templates/storefront/src/modules/order/components/item/index.tsx +57 -0
  243. package/templates/storefront/src/modules/order/components/items/index.tsx +44 -0
  244. package/templates/storefront/src/modules/order/components/onboarding-cta/index.tsx +28 -0
  245. package/templates/storefront/src/modules/order/components/order-details/index.tsx +64 -0
  246. package/templates/storefront/src/modules/order/components/order-summary/index.tsx +61 -0
  247. package/templates/storefront/src/modules/order/components/payment-details/index.tsx +64 -0
  248. package/templates/storefront/src/modules/order/components/shipping-details/index.tsx +74 -0
  249. package/templates/storefront/src/modules/order/components/transfer-actions/index.tsx +83 -0
  250. package/templates/storefront/src/modules/order/components/transfer-image/index.tsx +275 -0
  251. package/templates/storefront/src/modules/order/templates/order-completed-template.tsx +53 -0
  252. package/templates/storefront/src/modules/order/templates/order-details-template.tsx +46 -0
  253. package/templates/storefront/src/modules/products/components/thumbnail/index.tsx +70 -0
  254. package/templates/storefront/src/modules/products/templates/index.tsx +118 -0
  255. package/templates/storefront/src/modules/shipping/components/free-shipping-price-nudge/index.tsx +285 -0
  256. package/templates/storefront/src/modules/skeletons/components/skeleton-button/index.tsx +5 -0
  257. package/templates/storefront/src/modules/skeletons/components/skeleton-card-details/index.tsx +10 -0
  258. package/templates/storefront/src/modules/skeletons/components/skeleton-cart-item/index.tsx +35 -0
  259. package/templates/storefront/src/modules/skeletons/components/skeleton-cart-totals/index.tsx +30 -0
  260. package/templates/storefront/src/modules/skeletons/components/skeleton-code-form/index.tsx +13 -0
  261. package/templates/storefront/src/modules/skeletons/components/skeleton-line-item/index.tsx +35 -0
  262. package/templates/storefront/src/modules/skeletons/components/skeleton-order-confirmed-header/index.tsx +14 -0
  263. package/templates/storefront/src/modules/skeletons/components/skeleton-order-information/index.tsx +36 -0
  264. package/templates/storefront/src/modules/skeletons/components/skeleton-order-items/index.tsx +43 -0
  265. package/templates/storefront/src/modules/skeletons/components/skeleton-order-summary/index.tsx +15 -0
  266. package/templates/storefront/src/modules/skeletons/components/skeleton-product-preview/index.tsx +15 -0
  267. package/templates/storefront/src/modules/skeletons/templates/skeleton-cart-page/index.tsx +65 -0
  268. package/templates/storefront/src/modules/skeletons/templates/skeleton-order-confirmed/index.tsx +21 -0
  269. package/templates/storefront/src/modules/skeletons/templates/skeleton-product-grid/index.tsx +23 -0
  270. package/templates/storefront/src/modules/store/components/pagination/index.tsx +114 -0
  271. package/templates/storefront/src/modules/store/components/refinement-list/index.tsx +41 -0
  272. package/templates/storefront/src/modules/store/components/refinement-list/sort-products/index.tsx +48 -0
  273. package/templates/storefront/src/modules/store/templates/index.tsx +42 -0
  274. package/templates/storefront/src/modules/store/templates/paginated-products.tsx +80 -0
  275. package/templates/storefront/src/modules/ub/button.tsx +41 -0
  276. package/templates/storefront/src/modules/ub/campaign-blocks.tsx +197 -0
  277. package/templates/storefront/src/modules/ub/campaign-ended.tsx +28 -0
  278. package/templates/storefront/src/modules/ub/category-tiles.tsx +41 -0
  279. package/templates/storefront/src/modules/ub/countdown.tsx +46 -0
  280. package/templates/storefront/src/modules/ub/footer.tsx +56 -0
  281. package/templates/storefront/src/modules/ub/icons.tsx +42 -0
  282. package/templates/storefront/src/modules/ub/nav.tsx +90 -0
  283. package/templates/storefront/src/modules/ub/pdp/buy-box.tsx +140 -0
  284. package/templates/storefront/src/modules/ub/pdp/gallery.tsx +40 -0
  285. package/templates/storefront/src/modules/ub/product-card.tsx +66 -0
  286. package/templates/storefront/src/modules/ub/top-bar.tsx +20 -0
  287. package/templates/storefront/src/modules/ub/track-form.tsx +92 -0
  288. package/templates/storefront/src/proxy.ts +89 -0
  289. package/templates/storefront/src/styles/globals.css +98 -0
  290. package/templates/storefront/src/styles/ub/bundle.css +372 -0
  291. package/templates/storefront/src/styles/ub/fonts.css +17 -0
  292. package/templates/storefront/src/styles/ub/medusa-ui.css +90 -0
  293. package/templates/storefront/src/styles/ub/overrides.css +12 -0
  294. package/templates/storefront/src/styles/ub/tokens.css +9 -0
  295. package/templates/storefront/src/types/global.ts +24 -0
  296. package/templates/storefront/src/types/icon.ts +4 -0
  297. package/templates/storefront/tailwind.config.js +164 -0
  298. package/templates/storefront/themes/black-friday/campaign.en.json +31 -0
  299. package/templates/storefront/themes/black-friday/campaign.fr.json +31 -0
  300. package/templates/storefront/themes/black-friday/theme.json +9 -0
  301. package/templates/storefront/themes/cyber-monday/campaign.en.json +31 -0
  302. package/templates/storefront/themes/cyber-monday/campaign.fr.json +31 -0
  303. package/templates/storefront/themes/cyber-monday/theme.json +9 -0
  304. package/templates/storefront/themes/halloween/campaign.en.json +31 -0
  305. package/templates/storefront/themes/halloween/campaign.fr.json +31 -0
  306. package/templates/storefront/themes/halloween/theme.json +9 -0
  307. package/templates/storefront/themes/noel/campaign.en.json +31 -0
  308. package/templates/storefront/themes/noel/campaign.fr.json +31 -0
  309. package/templates/storefront/themes/noel/theme.json +9 -0
  310. package/templates/storefront/themes/nouvel-an/campaign.en.json +31 -0
  311. package/templates/storefront/themes/nouvel-an/campaign.fr.json +31 -0
  312. package/templates/storefront/themes/nouvel-an/theme.json +9 -0
  313. package/templates/storefront/themes/thanksgiving/campaign.en.json +31 -0
  314. package/templates/storefront/themes/thanksgiving/campaign.fr.json +31 -0
  315. package/templates/storefront/themes/thanksgiving/theme.json +9 -0
  316. package/templates/storefront/tsconfig.json +53 -0
@@ -0,0 +1,152 @@
1
+ import "server-only"
2
+
3
+ import fs from "node:fs"
4
+ import path from "node:path"
5
+ import { z } from "zod"
6
+ import { projectSchema, themeSchema, type Theme } from "./schema"
7
+
8
+ /**
9
+ * Configuration de l'instance. La boutique est fixée par la variable
10
+ * d'environnement BOUTIQUE et par rien d'autre : aucun paramètre d'URL,
11
+ * cookie ou en-tête ne peut en changer.
12
+ */
13
+ export type Boutique = {
14
+ project: string
15
+ slug: string
16
+ theme: Theme
17
+ storeName: string
18
+ currency: string
19
+ country: string
20
+ /** Pays livrés : "all" ou liste de codes pays (inclut toujours `country`) */
21
+ shipsTo: "all" | string[]
22
+ language: string
23
+ /** ex. "en-US" : pour Intl et <html lang> */
24
+ locale: string
25
+ port: number
26
+ publishableKey: string
27
+ merchantEmail?: string
28
+ /** URL publique des autres boutiques du projet (lien « campagne suivante ») */
29
+ siblings: Record<string, string>
30
+ }
31
+
32
+ export const CONFIG_DIR = process.env.CONFIG_DIR || "/app/config"
33
+ export const KEYS_DIR = process.env.KEYS_DIR || "/app/keys"
34
+ export const THEMES_DIR = path.join(process.cwd(), "themes")
35
+
36
+ export class BoutiqueConfigError extends Error {
37
+ constructor(message: string) {
38
+ super(`[boutique] ${message}`)
39
+ this.name = "BoutiqueConfigError"
40
+ }
41
+ }
42
+
43
+ function readJson(file: string): unknown {
44
+ let raw: string
45
+ try {
46
+ raw = fs.readFileSync(file, "utf8")
47
+ } catch {
48
+ throw new BoutiqueConfigError(`fichier introuvable : ${file}`)
49
+ }
50
+ try {
51
+ return JSON.parse(raw)
52
+ } catch (e) {
53
+ throw new BoutiqueConfigError(`JSON invalide dans ${file} : ${(e as Error).message}`)
54
+ }
55
+ }
56
+
57
+ export function parseOrThrow<T>(schema: z.ZodType<T>, data: unknown, file: string): T {
58
+ const res = schema.safeParse(data)
59
+ if (!res.success) {
60
+ throw new BoutiqueConfigError(`${file} invalide :\n${z.prettifyError(res.error)}`)
61
+ }
62
+ return res.data
63
+ }
64
+
65
+ export function listThemes(): string[] {
66
+ if (!fs.existsSync(THEMES_DIR)) return []
67
+ return fs
68
+ .readdirSync(THEMES_DIR, { withFileTypes: true })
69
+ .filter((d) => d.isDirectory() && fs.existsSync(path.join(THEMES_DIR, d.name, "theme.json")))
70
+ .map((d) => d.name)
71
+ }
72
+
73
+ function loadTheme(id: string): Theme {
74
+ const file = path.join(THEMES_DIR, id, "theme.json")
75
+ if (!fs.existsSync(file)) {
76
+ throw new BoutiqueConfigError(`thème inconnu « ${id} ». Thèmes disponibles : ${listThemes().join(", ")}`)
77
+ }
78
+ return parseOrThrow(themeSchema, readJson(file), file)
79
+ }
80
+
81
+ function load(): Boutique {
82
+ const slug = process.env.BOUTIQUE
83
+ if (!slug) {
84
+ throw new BoutiqueConfigError("variable d'environnement BOUTIQUE absente (ex. BOUTIQUE=yout-black-friday)")
85
+ }
86
+
87
+ const projectFile = path.join(/*turbopackIgnore: true*/ CONFIG_DIR, "project.json")
88
+ const project = parseOrThrow(projectSchema, readJson(projectFile), projectFile)
89
+
90
+ const entry = project.boutiques.find((b) => b.slug === slug)
91
+ if (!entry) {
92
+ throw new BoutiqueConfigError(
93
+ `boutique « ${slug} » absente de ${projectFile}. Boutiques déclarées : ${project.boutiques.map((b) => b.slug).join(", ")}`
94
+ )
95
+ }
96
+
97
+ const theme = loadTheme(entry.theme)
98
+
99
+ const keyFile = path.join(/*turbopackIgnore: true*/ KEYS_DIR, slug)
100
+ let publishableKey = ""
101
+ try {
102
+ publishableKey = fs.readFileSync(keyFile, "utf8").trim()
103
+ } catch {
104
+ // traité juste en dessous
105
+ }
106
+ if (!publishableKey.startsWith("pk_")) {
107
+ throw new BoutiqueConfigError(
108
+ `publishable key manquante ou invalide dans ${keyFile} (écrite par le backend au premier démarrage)`
109
+ )
110
+ }
111
+
112
+ const { defaults } = project
113
+ const country = entry.country ?? defaults.country
114
+ const shipsTo = entry.shipsTo ?? defaults.shipsTo
115
+ if (shipsTo !== "all" && !shipsTo.includes(country)) {
116
+ throw new BoutiqueConfigError(`${slug} : le pays principal « ${country} » doit faire partie de shipsTo`)
117
+ }
118
+ const language = defaults.language
119
+
120
+ return {
121
+ project: project.project,
122
+ slug,
123
+ theme,
124
+ storeName: entry.storeName ?? defaults.storeName,
125
+ currency: entry.currency ?? defaults.currency,
126
+ country,
127
+ shipsTo,
128
+ language,
129
+ locale: `${language}-${country.toUpperCase()}`,
130
+ port: entry.port,
131
+ publishableKey,
132
+ merchantEmail: defaults.merchantEmail,
133
+ siblings: Object.fromEntries(
134
+ project.boutiques.map((b) => [b.slug, b.url ?? `http://localhost:${b.port}`])
135
+ ),
136
+ }
137
+ }
138
+
139
+ let cached: Boutique | undefined
140
+
141
+ /**
142
+ * Boutique de cette instance. Lue une fois (au démarrage via instrumentation.ts),
143
+ * puis gardée en mémoire : modifier project.json demande un redémarrage (`apply`).
144
+ */
145
+ export function getBoutique(): Boutique {
146
+ if (!cached) {
147
+ cached = load()
148
+ // Lu par lib/util/money.ts, partagé avec les composants client (qui lisent <html lang>).
149
+ process.env.BOUTIQUE_LOCALE = cached.locale
150
+ }
151
+ return cached
152
+ }
@@ -0,0 +1,29 @@
1
+ import { Inter, Oswald, Outfit, Plus_Jakarta_Sans, Poppins, Sora, Space_Grotesk } from "next/font/google"
2
+ import type { FontId, Theme } from "./schema"
3
+
4
+ // Polices des presets (design/bundle.css). Servies par next/font (auto-hébergées) :
5
+ // seules les 2 polices du thème de l'instance sont appliquées.
6
+ // Chaque police expose --nf-<id>, repris par --font-<id> dans styles/ub/fonts.css.
7
+ const inter = Inter({ subsets: ["latin"], weight: ["400", "500", "600", "700"], variable: "--nf-inter", preload: false })
8
+ const jakarta = Plus_Jakarta_Sans({ subsets: ["latin"], weight: ["500", "700", "800"], variable: "--nf-jakarta", preload: false })
9
+ const oswald = Oswald({ subsets: ["latin"], weight: ["500", "700"], variable: "--nf-oswald", preload: false })
10
+ const outfit = Outfit({ subsets: ["latin"], weight: ["400", "500", "600", "700"], variable: "--nf-outfit", preload: false })
11
+ const poppins = Poppins({ subsets: ["latin"], weight: ["400", "500", "600", "700"], variable: "--nf-poppins", preload: false })
12
+ const sora = Sora({ subsets: ["latin"], weight: ["400", "600", "700"], variable: "--nf-sora", preload: false })
13
+ const spaceGrotesk = Space_Grotesk({ subsets: ["latin"], weight: ["500", "700"], variable: "--nf-space-grotesk", preload: false })
14
+
15
+ const fonts: Partial<Record<FontId, { variable: string }>> = {
16
+ inter,
17
+ jakarta,
18
+ oswald,
19
+ outfit,
20
+ poppins,
21
+ sora,
22
+ "space-grotesk": spaceGrotesk,
23
+ }
24
+
25
+ /** Classes à poser sur <html> : variables CSS des polices du thème (+ Inter, police des formulaires du starter). */
26
+ export function themeFontClasses(theme: Theme): string {
27
+ const ids = new Set<FontId>([theme.fonts.head, theme.fonts.body, "inter"])
28
+ return [...ids].map((id) => fonts[id]?.variable).filter(Boolean).join(" ")
29
+ }
@@ -0,0 +1,34 @@
1
+ "use client"
2
+
3
+ import { createContext, useContext } from "react"
4
+
5
+ type Dict = Record<string, string>
6
+
7
+ const I18nContext = createContext<Dict>({})
8
+
9
+ /**
10
+ * Dictionnaire de l'instance pour les composants client (checkout, panier, compte).
11
+ * Rempli par le layout racine (serveur) avec locales/<lang>.json + repli anglais.
12
+ */
13
+ export function I18nProvider({ messages, children }: { messages: Dict; children: React.ReactNode }) {
14
+ return <I18nContext.Provider value={messages}>{children}</I18nContext.Provider>
15
+ }
16
+
17
+ export function format(text: string, vars?: Record<string, string | number>) {
18
+ return vars ? text.replace(/\{(\w+)\}/g, (m, k: string) => (k in vars ? String(vars[k]) : m)) : text
19
+ }
20
+
21
+ /**
22
+ * Traduction côté client : mêmes clés et mêmes variables que t() côté serveur.
23
+ * Une clé inconnue est renvoyée telle quelle : les composants partagés (Input, Checkbox…)
24
+ * peuvent donc recevoir une clé ou un texte déjà traduit.
25
+ */
26
+ export function useT() {
27
+ const messages = useContext(I18nContext)
28
+ return (key: string, vars?: Record<string, string | number>) => format(messages[key] ?? key, vars)
29
+ }
30
+
31
+ /** Texte traduit dans du JSX, utilisable depuis un composant serveur ou client : <T k="cart.title" />. */
32
+ export function T({ k, vars }: { k: string; vars?: Record<string, string | number> }) {
33
+ return <>{useT()(k, vars)}</>
34
+ }
@@ -0,0 +1,43 @@
1
+ import "server-only"
2
+
3
+ import fs from "node:fs"
4
+ import path from "node:path"
5
+ import { getBoutique } from "./config"
6
+
7
+ type Dict = Record<string, string>
8
+
9
+ const LOCALES_DIR = path.join(process.cwd(), "locales")
10
+ const dicts = new Map<string, Dict>()
11
+
12
+ function dict(lang: string): Dict {
13
+ if (!dicts.has(lang)) {
14
+ const file = path.join(LOCALES_DIR, `${lang}.json`)
15
+ dicts.set(lang, fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, "utf8")) : {})
16
+ }
17
+ return dicts.get(lang)!
18
+ }
19
+
20
+ /**
21
+ * Traduction des textes d'interface (locales/<lang>.json), repli en anglais.
22
+ * Variables : t("cart.count", { count: 2 }) avec "{count} items" dans le fichier.
23
+ * Une clé absente partout renvoie la clé (signalé par `check`), jamais une chaîne vide.
24
+ */
25
+ export function t(key: string, vars?: Record<string, string | number>): string {
26
+ const { language } = getBoutique()
27
+ const text = dict(language)[key] ?? dict("en")[key] ?? key
28
+ return vars ? text.replace(/\{(\w+)\}/g, (m, k: string) => (k in vars ? String(vars[k]) : m)) : text
29
+ }
30
+
31
+ /** Sous-ensemble du dictionnaire à passer à un composant client (préfixe "cart." etc.). */
32
+ export function messages(prefix: string): Dict {
33
+ const { language } = getBoutique()
34
+ const out: Dict = {}
35
+ for (const d of [dict("en"), dict(language)]) {
36
+ for (const [k, v] of Object.entries(d)) if (k.startsWith(prefix)) out[k] = v
37
+ }
38
+ return out
39
+ }
40
+
41
+ export function formatDate(iso: string, opts: Intl.DateTimeFormatOptions = { dateStyle: "long" }): string {
42
+ return new Intl.DateTimeFormat(getBoutique().locale, opts).format(new Date(iso))
43
+ }
@@ -0,0 +1,131 @@
1
+ import { z } from "zod"
2
+
3
+ /**
4
+ * Schémas partagés : config/project.json, themes/<id>/theme.json,
5
+ * config/campaigns/<slug>.json. Les textes affichés sont du texte simple :
6
+ * aucun caractère < ou > (pas de HTML), longueurs bornées.
7
+ */
8
+
9
+ const plain = (max: number) =>
10
+ z
11
+ .string()
12
+ .trim()
13
+ .max(max)
14
+ .refine((v) => !/[<>]/.test(v), "texte simple uniquement (pas de HTML)")
15
+
16
+ export const SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
17
+
18
+ const currency = z.string().regex(/^[a-z]{3}$/, "code devise ISO 4217 en minuscules (ex. usd)")
19
+ const country = z.string().regex(/^[a-z]{2}$/, "code pays ISO 3166-1 alpha-2 en minuscules (ex. us)")
20
+ const language = z.string().regex(/^[a-z]{2}$/, "code langue ISO 639-1 (ex. en)")
21
+
22
+ /** Pays livrés : "all" (monde entier) ou une liste de codes pays. */
23
+ const shipsTo = z.union([z.literal("all"), z.array(country).min(1)])
24
+
25
+ const overridable = {
26
+ storeName: plain(60).min(1),
27
+ currency,
28
+ /** Pays principal : format des prix et des dates (ex. en-US), pays proposé par défaut au checkout. */
29
+ country,
30
+ shipsTo,
31
+ }
32
+
33
+ export const projectSchema = z.object({
34
+ project: z.string().regex(SLUG),
35
+ templateVersion: z.string().optional(),
36
+ defaults: z
37
+ .object({
38
+ ...overridable,
39
+ shipsTo: shipsTo.default("all"),
40
+ language: language.default("en"),
41
+ merchantEmail: z.email().optional(),
42
+ email: z.object({ mode: z.enum(["platform", "merchant"]) }).loose().optional(),
43
+ })
44
+ .loose(),
45
+ boutiques: z
46
+ .array(
47
+ z
48
+ .object({
49
+ slug: z.string().regex(SLUG, "slug : minuscules, chiffres et tirets"),
50
+ theme: z.string().regex(SLUG),
51
+ port: z.number().int().min(1).max(65535),
52
+ url: z.url().optional(),
53
+ storeName: overridable.storeName.optional(),
54
+ currency: overridable.currency.optional(),
55
+ country: overridable.country.optional(),
56
+ shipsTo: overridable.shipsTo.optional(),
57
+ language: z.never({ error: "la langue se règle dans defaults, pas par boutique" }).optional(),
58
+ })
59
+ .strict()
60
+ )
61
+ .min(1, "au moins une boutique")
62
+ .refine((b) => new Set(b.map((x) => x.slug)).size === b.length, "slug en double"),
63
+ })
64
+
65
+ export type Project = z.infer<typeof projectSchema>
66
+
67
+ export const themeSchema = z.object({
68
+ id: z.string().regex(SLUG),
69
+ name: z.string().min(1),
70
+ reference: z.string().optional(),
71
+ fonts: z.object({
72
+ head: z.enum(["inter", "jakarta", "oswald", "outfit", "poppins", "serif", "sora", "space-grotesk"]),
73
+ body: z.enum(["inter", "jakarta", "oswald", "outfit", "poppins", "serif", "sora", "space-grotesk"]),
74
+ }),
75
+ })
76
+
77
+ export type Theme = z.infer<typeof themeSchema>
78
+ export type FontId = Theme["fonts"]["head"]
79
+
80
+ const date = z.iso.datetime({ offset: true })
81
+
82
+ /** Textes par défaut d'un thème (themes/<id>/campaign.<lang>.json), avec variables {discount}, {endDate}, {freeShipping}. */
83
+ export const campaignTextsSchema = z.object({
84
+ topbar: z.array(plain(80)).max(5),
85
+ hero: z.object({
86
+ eyebrow: plain(40),
87
+ title: plain(60).min(1),
88
+ highlight: plain(40).optional(),
89
+ sub: plain(160),
90
+ cta: plain(30),
91
+ cta2: plain(30).optional(),
92
+ image: z.string().max(500).optional(),
93
+ bubble: z.array(plain(12)).max(3),
94
+ }),
95
+ promo: z.object({
96
+ eyebrow: plain(40),
97
+ title: plain(60),
98
+ text: plain(160),
99
+ cta: plain(30),
100
+ seal: plain(20).optional(),
101
+ image: z.string().max(500).optional(),
102
+ }),
103
+ sections: z
104
+ .object({ categories: plain(40), products: plain(40) })
105
+ .optional(),
106
+ })
107
+
108
+ export type CampaignTexts = z.infer<typeof campaignTextsSchema>
109
+
110
+ /** Fichier d'une boutique (config/campaigns/<slug>.json) : tout est optionnel sauf les dates, et remplace les textes du thème. */
111
+ export const campaignFileSchema = z
112
+ .object({
113
+ startsAt: date,
114
+ endsAt: date,
115
+ nextCampaign: z.string().regex(SLUG).optional(),
116
+ vars: z
117
+ .object({ discount: plain(12), freeShipping: plain(20) })
118
+ .partial()
119
+ .optional(),
120
+ topbar: campaignTextsSchema.shape.topbar.optional(),
121
+ hero: campaignTextsSchema.shape.hero.partial().optional(),
122
+ promo: campaignTextsSchema.shape.promo.partial().optional(),
123
+ sections: campaignTextsSchema.shape.sections.unwrap().partial().optional(),
124
+ /** Logo de la boutique (URL du stockage), aussi utilisé comme favicon */
125
+ logo: z.url().max(500).optional(),
126
+ /** undefined = selon le thème */
127
+ showCountdown: z.boolean().optional(),
128
+ })
129
+ .refine((c) => new Date(c.startsAt) < new Date(c.endsAt), "startsAt doit précéder endsAt")
130
+
131
+ export type CampaignFile = z.infer<typeof campaignFileSchema>
@@ -0,0 +1,39 @@
1
+ import "server-only"
2
+
3
+ import { getBoutique } from "@lib/boutique/config"
4
+ import Medusa, { FetchArgs, FetchInput } from "@medusajs/js-sdk"
5
+
6
+ // URL interne du backend (réseau Docker) : jamais exposée au navigateur.
7
+ const MEDUSA_BACKEND_URL = process.env.MEDUSA_BACKEND_URL || "http://localhost:9000"
8
+
9
+ let client: Medusa | undefined
10
+
11
+ /**
12
+ * Client Medusa de la boutique : la publishable key vient de KEYS_DIR/<slug>,
13
+ * lue à l'exécution (pas de variable NEXT_PUBLIC_* figée au build).
14
+ * Créé au premier usage pour que `next build` n'ait besoin d'aucune boutique.
15
+ */
16
+ function getClient(): Medusa {
17
+ if (client) return client
18
+
19
+ const boutique = getBoutique()
20
+ const medusa = new Medusa({
21
+ baseUrl: MEDUSA_BACKEND_URL,
22
+ debug: process.env.NODE_ENV === "development",
23
+ publishableKey: boutique.publishableKey,
24
+ })
25
+
26
+ // Langue fixée par l'instance : les traductions Medusa suivent la langue du projet.
27
+ const originalFetch = medusa.client.fetch.bind(medusa.client)
28
+ medusa.client.fetch = async <T>(input: FetchInput, init?: FetchArgs): Promise<T> => {
29
+ const headers = { "x-medusa-locale": boutique.locale, ...(init?.headers ?? {}) }
30
+ return originalFetch(input, { ...init, headers })
31
+ }
32
+
33
+ client = medusa
34
+ return client
35
+ }
36
+
37
+ export const sdk = new Proxy({} as Medusa, {
38
+ get: (_target, prop) => Reflect.get(getClient(), prop),
39
+ })
@@ -0,0 +1,75 @@
1
+ import React from "react"
2
+ import { CreditCard } from "@medusajs/icons"
3
+
4
+ import Ideal from "@modules/common/icons/ideal"
5
+ import Bancontact from "@modules/common/icons/bancontact"
6
+ import PayPal from "@modules/common/icons/paypal"
7
+
8
+ /* Map of payment provider_id to their title and icon. Add in any payment providers you want to use. */
9
+ export const paymentInfoMap: Record<
10
+ string,
11
+ { title: string; icon: React.JSX.Element }
12
+ > = {
13
+ pp_stripe_stripe: {
14
+ title: "Credit card",
15
+ icon: <CreditCard />,
16
+ },
17
+ "pp_medusa-payments_default": {
18
+ title: "Credit card",
19
+ icon: <CreditCard />,
20
+ },
21
+ "pp_stripe-ideal_stripe": {
22
+ title: "iDeal",
23
+ icon: <Ideal />,
24
+ },
25
+ "pp_stripe-bancontact_stripe": {
26
+ title: "Bancontact",
27
+ icon: <Bancontact />,
28
+ },
29
+ pp_paypal_paypal: {
30
+ title: "PayPal",
31
+ icon: <PayPal />,
32
+ },
33
+ pp_system_default: {
34
+ title: "Manual Payment",
35
+ icon: <CreditCard />,
36
+ },
37
+ // Add more payment providers here
38
+ }
39
+
40
+ // This only checks if it is native stripe or medusa payments for card payments, it ignores the other stripe-based providers
41
+ export const isStripeLike = (providerId?: string) => {
42
+ return (
43
+ providerId?.startsWith("pp_stripe_") || providerId?.startsWith("pp_medusa-")
44
+ )
45
+ }
46
+
47
+ export const isPaypal = (providerId?: string) => {
48
+ return providerId?.startsWith("pp_paypal")
49
+ }
50
+ export const isManual = (providerId?: string) => {
51
+ return providerId?.startsWith("pp_system_default")
52
+ }
53
+
54
+ // Add currencies that don't need to be divided by 100
55
+ export const noDivisionCurrencies = [
56
+ "krw",
57
+ "jpy",
58
+ "vnd",
59
+ "clp",
60
+ "pyg",
61
+ "xaf",
62
+ "xof",
63
+ "bif",
64
+ "djf",
65
+ "gnf",
66
+ "kmf",
67
+ "mga",
68
+ "rwf",
69
+ "xpf",
70
+ "htg",
71
+ "vuv",
72
+ "xag",
73
+ "xdr",
74
+ "xau",
75
+ ]
@@ -0,0 +1,34 @@
1
+ "use client"
2
+
3
+ import React, { createContext, useContext } from "react"
4
+
5
+ interface ModalContext {
6
+ close: () => void
7
+ }
8
+
9
+ const ModalContext = createContext<ModalContext | null>(null)
10
+
11
+ interface ModalProviderProps {
12
+ children?: React.ReactNode
13
+ close: () => void
14
+ }
15
+
16
+ export const ModalProvider = ({ children, close }: ModalProviderProps) => {
17
+ return (
18
+ <ModalContext.Provider
19
+ value={{
20
+ close,
21
+ }}
22
+ >
23
+ {children}
24
+ </ModalContext.Provider>
25
+ )
26
+ }
27
+
28
+ export const useModal = () => {
29
+ const context = useContext(ModalContext)
30
+ if (context === null) {
31
+ throw new Error("useModal must be used within a ModalProvider")
32
+ }
33
+ return context
34
+ }