@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,54 @@
1
+ "use client"
2
+
3
+
4
+ import { T } from "@lib/boutique/i18n-client"
5
+ import { Heading, Text, clx } from "@medusajs/ui"
6
+
7
+ import PaymentButton from "../payment-button"
8
+ import { useSearchParams } from "next/navigation"
9
+
10
+ const Review = ({ cart }: { cart: any }) => {
11
+ const searchParams = useSearchParams()
12
+
13
+ const isOpen = searchParams.get("step") === "review"
14
+
15
+ const paidByGiftcard =
16
+ cart?.gift_cards && cart?.gift_cards?.length > 0 && cart?.total === 0
17
+
18
+ const previousStepsCompleted =
19
+ cart.shipping_address &&
20
+ cart.shipping_methods.length > 0 &&
21
+ (cart.payment_collection || paidByGiftcard)
22
+
23
+ return (
24
+ <div className="bg-white">
25
+ <div className="flex flex-row items-center justify-between mb-6">
26
+ <Heading
27
+ level="h2"
28
+ className={clx(
29
+ "flex flex-row text-3xl-regular gap-x-2 items-baseline",
30
+ {
31
+ "opacity-50 pointer-events-none select-none": !isOpen,
32
+ }
33
+ )}
34
+ >
35
+ <T k="checkout.review" />
36
+ </Heading>
37
+ </div>
38
+ {isOpen && previousStepsCompleted && (
39
+ <>
40
+ <div className="flex items-start gap-x-1 w-full mb-6">
41
+ <div className="w-full">
42
+ <Text className="txt-medium-plus text-ui-fg-base mb-1">
43
+ <T k="checkout.terms" />
44
+ </Text>
45
+ </div>
46
+ </div>
47
+ <PaymentButton cart={cart} data-testid="submit-order-button" />
48
+ </>
49
+ )}
50
+ </div>
51
+ )
52
+ }
53
+
54
+ export default Review
@@ -0,0 +1,417 @@
1
+ "use client"
2
+
3
+
4
+ import { T } from "@lib/boutique/i18n-client"
5
+ import { Radio, RadioGroup } from "@headlessui/react"
6
+ import { setShippingMethod } from "@lib/data/cart"
7
+ import { calculatePriceForShippingOption } from "@lib/data/fulfillment"
8
+ import { convertToLocale } from "@lib/util/money"
9
+ import { CheckCircleSolid, Loader } from "@medusajs/icons"
10
+ import { HttpTypes } from "@medusajs/types"
11
+ import { Button, clx, Heading, Text } from "@medusajs/ui"
12
+ import ErrorMessage from "@modules/checkout/components/error-message"
13
+ import Divider from "@modules/common/components/divider"
14
+ import MedusaRadio from "@modules/common/components/radio"
15
+ import { usePathname, useRouter, useSearchParams } from "next/navigation"
16
+ import { useEffect, useState } from "react"
17
+
18
+ // Champs demandés par listCartOptions (fields) mais absents du type StoreCartShippingOption.
19
+ type ShippingOptionWithZone = HttpTypes.StoreCartShippingOption & {
20
+ service_zone?: {
21
+ fulfillment_set?: { type?: string; location?: { address?: HttpTypes.StoreCartAddress } }
22
+ }
23
+ }
24
+
25
+ const PICKUP_OPTION_ON = "__PICKUP_ON"
26
+ const PICKUP_OPTION_OFF = "__PICKUP_OFF"
27
+
28
+ type ShippingProps = {
29
+ cart: HttpTypes.StoreCart
30
+ availableShippingMethods: HttpTypes.StoreCartShippingOption[] | null
31
+ }
32
+
33
+ function formatAddress(address?: HttpTypes.StoreCartAddress) {
34
+ if (!address) {
35
+ return ""
36
+ }
37
+
38
+ let ret = ""
39
+
40
+ if (address.address_1) {
41
+ ret += ` ${address.address_1}`
42
+ }
43
+
44
+ if (address.address_2) {
45
+ ret += `, ${address.address_2}`
46
+ }
47
+
48
+ if (address.postal_code) {
49
+ ret += `, ${address.postal_code} ${address.city}`
50
+ }
51
+
52
+ if (address.country_code) {
53
+ ret += `, ${address.country_code.toUpperCase()}`
54
+ }
55
+
56
+ return ret
57
+ }
58
+
59
+ const Shipping: React.FC<ShippingProps> = ({
60
+ cart,
61
+ availableShippingMethods,
62
+ }) => {
63
+ const [isLoading, setIsLoading] = useState(false)
64
+ const [isLoadingPrices, setIsLoadingPrices] = useState(true)
65
+
66
+ const [showPickupOptions, setShowPickupOptions] =
67
+ useState<string>(PICKUP_OPTION_OFF)
68
+ const [calculatedPricesMap, setCalculatedPricesMap] = useState<
69
+ Record<string, number>
70
+ >({})
71
+ const [error, setError] = useState<string | null>(null)
72
+ const [shippingMethodId, setShippingMethodId] = useState<string | null>(
73
+ cart.shipping_methods?.at(-1)?.shipping_option_id || null
74
+ )
75
+
76
+ const searchParams = useSearchParams()
77
+ const router = useRouter()
78
+ const pathname = usePathname()
79
+
80
+ const isOpen = searchParams.get("step") === "delivery"
81
+
82
+ const _shippingMethods = availableShippingMethods?.filter(
83
+ (sm) => (sm as ShippingOptionWithZone).service_zone?.fulfillment_set?.type !== "pickup"
84
+ )
85
+
86
+ const _pickupMethods = availableShippingMethods?.filter(
87
+ (sm) => (sm as ShippingOptionWithZone).service_zone?.fulfillment_set?.type === "pickup"
88
+ )
89
+
90
+ const hasPickupOptions = !!_pickupMethods?.length
91
+
92
+ useEffect(() => {
93
+ setIsLoadingPrices(true)
94
+
95
+ if (_shippingMethods?.length) {
96
+ const promises = _shippingMethods
97
+ .filter((sm) => sm.price_type === "calculated")
98
+ .map((sm) => calculatePriceForShippingOption(sm.id, cart.id))
99
+
100
+ if (promises.length) {
101
+ Promise.allSettled(promises).then((res) => {
102
+ const pricesMap: Record<string, number> = {}
103
+ res
104
+ .filter((r) => r.status === "fulfilled")
105
+ .forEach((p) => (pricesMap[p.value?.id || ""] = p.value?.amount!))
106
+
107
+ setCalculatedPricesMap(pricesMap)
108
+ setIsLoadingPrices(false)
109
+ })
110
+ }
111
+ }
112
+
113
+ if (_pickupMethods?.find((m) => m.id === shippingMethodId)) {
114
+ setShowPickupOptions(PICKUP_OPTION_ON)
115
+ }
116
+ }, [availableShippingMethods])
117
+
118
+ const handleEdit = () => {
119
+ router.push(pathname + "?step=delivery", { scroll: false })
120
+ }
121
+
122
+ const handleSubmit = () => {
123
+ router.push(pathname + "?step=payment", { scroll: false })
124
+ }
125
+
126
+ const handleSetShippingMethod = async (
127
+ id: string,
128
+ variant: "shipping" | "pickup"
129
+ ) => {
130
+ setError(null)
131
+
132
+ if (variant === "pickup") {
133
+ setShowPickupOptions(PICKUP_OPTION_ON)
134
+ } else {
135
+ setShowPickupOptions(PICKUP_OPTION_OFF)
136
+ }
137
+
138
+ let currentId: string | null = null
139
+ setIsLoading(true)
140
+ setShippingMethodId((prev) => {
141
+ currentId = prev
142
+ return id
143
+ })
144
+
145
+ await setShippingMethod({ cartId: cart.id, shippingMethodId: id })
146
+ .catch((err) => {
147
+ setShippingMethodId(currentId)
148
+
149
+ setError(err.message)
150
+ })
151
+ .finally(() => {
152
+ setIsLoading(false)
153
+ })
154
+ }
155
+
156
+ useEffect(() => {
157
+ setError(null)
158
+ }, [isOpen])
159
+
160
+ return (
161
+ <div className="bg-white">
162
+ <div className="flex flex-row items-center justify-between mb-6">
163
+ <Heading
164
+ level="h2"
165
+ className={clx(
166
+ "flex flex-row text-3xl-regular gap-x-2 items-baseline",
167
+ {
168
+ "opacity-50 pointer-events-none select-none":
169
+ !isOpen && cart.shipping_methods?.length === 0,
170
+ }
171
+ )}
172
+ >
173
+ <T k="checkout.delivery" />
174
+ {!isOpen && (cart.shipping_methods?.length ?? 0) > 0 && (
175
+ <CheckCircleSolid />
176
+ )}
177
+ </Heading>
178
+ {!isOpen &&
179
+ cart?.shipping_address &&
180
+ cart?.billing_address &&
181
+ cart?.email && (
182
+ <Text>
183
+ <button
184
+ onClick={handleEdit}
185
+ className="text-ui-fg-interactive hover:text-ui-fg-interactive-hover"
186
+ data-testid="edit-delivery-button"
187
+ >
188
+ <T k="form.edit" />
189
+ </button>
190
+ </Text>
191
+ )}
192
+ </div>
193
+ {isOpen ? (
194
+ <>
195
+ <div className="grid">
196
+ <div className="flex flex-col">
197
+ <span className="font-medium txt-medium text-ui-fg-base">
198
+ <T k="checkout.shippingMethod" />
199
+ </span>
200
+ <span className="mb-4 text-ui-fg-muted txt-medium">
201
+ <T k="checkout.shippingHow" />
202
+ </span>
203
+ </div>
204
+ <div data-testid="delivery-options-container">
205
+ <div className="pb-8 md:pt-0 pt-2">
206
+ {hasPickupOptions && (
207
+ <RadioGroup
208
+ value={showPickupOptions}
209
+ onChange={(value) => {
210
+ const id = _pickupMethods.find(
211
+ (option) => !option.insufficient_inventory
212
+ )?.id
213
+
214
+ if (id) {
215
+ handleSetShippingMethod(id, "pickup")
216
+ }
217
+ }}
218
+ >
219
+ <Radio
220
+ value={PICKUP_OPTION_ON}
221
+ data-testid="delivery-option-radio"
222
+ className={clx(
223
+ "flex items-center justify-between text-small-regular cursor-pointer py-4 border rounded-rounded px-8 mb-2 hover:shadow-borders-interactive-with-active",
224
+ {
225
+ "border-ui-border-interactive":
226
+ showPickupOptions === PICKUP_OPTION_ON,
227
+ }
228
+ )}
229
+ >
230
+ <div className="flex items-center gap-x-4">
231
+ <MedusaRadio
232
+ checked={showPickupOptions === PICKUP_OPTION_ON}
233
+ />
234
+ <span className="text-base-regular">
235
+ <T k="checkout.pickup" />
236
+ </span>
237
+ </div>
238
+ <span className="justify-self-end text-ui-fg-base">
239
+ -
240
+ </span>
241
+ </Radio>
242
+ </RadioGroup>
243
+ )}
244
+ <RadioGroup
245
+ value={shippingMethodId}
246
+ onChange={(v) => {
247
+ if (v) {
248
+ return handleSetShippingMethod(v, "shipping")
249
+ }
250
+ }}
251
+ >
252
+ {_shippingMethods?.map((option) => {
253
+ const isDisabled =
254
+ option.price_type === "calculated" &&
255
+ !isLoadingPrices &&
256
+ typeof calculatedPricesMap[option.id] !== "number"
257
+
258
+ return (
259
+ <Radio
260
+ key={option.id}
261
+ value={option.id}
262
+ data-testid="delivery-option-radio"
263
+ disabled={isDisabled}
264
+ className={clx(
265
+ "flex items-center justify-between text-small-regular cursor-pointer py-4 border rounded-rounded px-8 mb-2 hover:shadow-borders-interactive-with-active",
266
+ {
267
+ "border-ui-border-interactive":
268
+ option.id === shippingMethodId,
269
+ "hover:shadow-brders-none cursor-not-allowed":
270
+ isDisabled,
271
+ }
272
+ )}
273
+ >
274
+ <div className="flex items-center gap-x-4">
275
+ <MedusaRadio
276
+ checked={option.id === shippingMethodId}
277
+ />
278
+ <span className="text-base-regular">
279
+ {option.name}
280
+ </span>
281
+ </div>
282
+ <span className="justify-self-end text-ui-fg-base">
283
+ {option.price_type === "flat" ? (
284
+ convertToLocale({
285
+ amount: option.amount!,
286
+ currency_code: cart?.currency_code,
287
+ })
288
+ ) : calculatedPricesMap[option.id] ? (
289
+ convertToLocale({
290
+ amount: calculatedPricesMap[option.id],
291
+ currency_code: cart?.currency_code,
292
+ })
293
+ ) : isLoadingPrices ? (
294
+ <Loader />
295
+ ) : (
296
+ "-"
297
+ )}
298
+ </span>
299
+ </Radio>
300
+ )
301
+ })}
302
+ </RadioGroup>
303
+ </div>
304
+ </div>
305
+ </div>
306
+
307
+ {showPickupOptions === PICKUP_OPTION_ON && (
308
+ <div className="grid">
309
+ <div className="flex flex-col">
310
+ <span className="font-medium txt-medium text-ui-fg-base">
311
+ <T k="checkout.store" />
312
+ </span>
313
+ <span className="mb-4 text-ui-fg-muted txt-medium">
314
+ <T k="checkout.chooseStore" />
315
+ </span>
316
+ </div>
317
+ <div data-testid="delivery-options-container">
318
+ <div className="pb-8 md:pt-0 pt-2">
319
+ <RadioGroup
320
+ value={shippingMethodId}
321
+ onChange={(v) => {
322
+ if (v) {
323
+ return handleSetShippingMethod(v, "pickup")
324
+ }
325
+ }}
326
+ >
327
+ {_pickupMethods?.map((option) => {
328
+ return (
329
+ <Radio
330
+ key={option.id}
331
+ value={option.id}
332
+ disabled={option.insufficient_inventory}
333
+ data-testid="delivery-option-radio"
334
+ className={clx(
335
+ "flex items-center justify-between text-small-regular cursor-pointer py-4 border rounded-rounded px-8 mb-2 hover:shadow-borders-interactive-with-active",
336
+ {
337
+ "border-ui-border-interactive":
338
+ option.id === shippingMethodId,
339
+ "hover:shadow-brders-none cursor-not-allowed":
340
+ option.insufficient_inventory,
341
+ }
342
+ )}
343
+ >
344
+ <div className="flex items-start gap-x-4">
345
+ <MedusaRadio
346
+ checked={option.id === shippingMethodId}
347
+ />
348
+ <div className="flex flex-col">
349
+ <span className="text-base-regular">
350
+ {option.name}
351
+ </span>
352
+ <span className="text-base-regular text-ui-fg-muted">
353
+ {formatAddress(
354
+ (option as ShippingOptionWithZone).service_zone?.fulfillment_set?.location
355
+ ?.address
356
+ )}
357
+ </span>
358
+ </div>
359
+ </div>
360
+ <span className="justify-self-end text-ui-fg-base">
361
+ {convertToLocale({
362
+ amount: option.amount!,
363
+ currency_code: cart?.currency_code,
364
+ })}
365
+ </span>
366
+ </Radio>
367
+ )
368
+ })}
369
+ </RadioGroup>
370
+ </div>
371
+ </div>
372
+ </div>
373
+ )}
374
+
375
+ <div>
376
+ <ErrorMessage
377
+ error={error}
378
+ data-testid="delivery-option-error-message"
379
+ />
380
+ <Button
381
+ size="large"
382
+ className="mt"
383
+ onClick={handleSubmit}
384
+ isLoading={isLoading}
385
+ disabled={!cart.shipping_methods?.[0]}
386
+ data-testid="submit-delivery-option-button"
387
+ >
388
+ <T k="checkout.continuePayment" />
389
+ </Button>
390
+ </div>
391
+ </>
392
+ ) : (
393
+ <div>
394
+ <div className="text-small-regular">
395
+ {cart && (cart.shipping_methods?.length ?? 0) > 0 && (
396
+ <div className="flex flex-col w-1/3">
397
+ <Text className="txt-medium-plus text-ui-fg-base mb-1">
398
+ <T k="checkout.method" />
399
+ </Text>
400
+ <Text className="txt-medium text-ui-fg-subtle">
401
+ {cart.shipping_methods!.at(-1)!.name}{" "}
402
+ {convertToLocale({
403
+ amount: cart.shipping_methods!.at(-1)!.amount!,
404
+ currency_code: cart?.currency_code,
405
+ })}
406
+ </Text>
407
+ </div>
408
+ )}
409
+ </div>
410
+ </div>
411
+ )}
412
+ <Divider className="mt-8" />
413
+ </div>
414
+ )
415
+ }
416
+
417
+ export default Shipping
@@ -0,0 +1,219 @@
1
+ import { HttpTypes } from "@medusajs/types"
2
+ import { Container } from "@medusajs/ui"
3
+ import Checkbox from "@modules/common/components/checkbox"
4
+ import Input from "@modules/common/components/input"
5
+ import { mapKeys } from "lodash"
6
+ import React, { useEffect, useMemo, useState } from "react"
7
+ import AddressSelect from "../address-select"
8
+ import CountrySelect from "../country-select"
9
+
10
+ const ShippingAddress = ({
11
+ customer,
12
+ cart,
13
+ checked,
14
+ onChange,
15
+ }: {
16
+ customer: HttpTypes.StoreCustomer | null
17
+ cart: HttpTypes.StoreCart | null
18
+ checked: boolean
19
+ onChange: () => void
20
+ }) => {
21
+ const [formData, setFormData] = useState<Record<string, any>>({
22
+ "shipping_address.first_name": cart?.shipping_address?.first_name || "",
23
+ "shipping_address.last_name": cart?.shipping_address?.last_name || "",
24
+ "shipping_address.address_1": cart?.shipping_address?.address_1 || "",
25
+ "shipping_address.company": cart?.shipping_address?.company || "",
26
+ "shipping_address.postal_code": cart?.shipping_address?.postal_code || "",
27
+ "shipping_address.city": cart?.shipping_address?.city || "",
28
+ "shipping_address.country_code": cart?.shipping_address?.country_code || "",
29
+ "shipping_address.province": cart?.shipping_address?.province || "",
30
+ "shipping_address.phone": cart?.shipping_address?.phone || "",
31
+ email: cart?.email || "",
32
+ })
33
+
34
+ const countriesInRegion = useMemo(
35
+ () => cart?.region?.countries?.map((c) => c.iso_2),
36
+ [cart?.region]
37
+ )
38
+
39
+ // check if customer has saved addresses that are in the current region
40
+ const addressesInRegion = useMemo(
41
+ () =>
42
+ customer?.addresses.filter(
43
+ (a) => a.country_code && countriesInRegion?.includes(a.country_code)
44
+ ),
45
+ [customer?.addresses, countriesInRegion]
46
+ )
47
+
48
+ const setFormAddress = (
49
+ address?: HttpTypes.StoreCartAddress,
50
+ email?: string
51
+ ) => {
52
+ address &&
53
+ setFormData((prevState: Record<string, any>) => ({
54
+ ...prevState,
55
+ "shipping_address.first_name": address?.first_name || "",
56
+ "shipping_address.last_name": address?.last_name || "",
57
+ "shipping_address.address_1": address?.address_1 || "",
58
+ "shipping_address.company": address?.company || "",
59
+ "shipping_address.postal_code": address?.postal_code || "",
60
+ "shipping_address.city": address?.city || "",
61
+ "shipping_address.country_code": address?.country_code || "",
62
+ "shipping_address.province": address?.province || "",
63
+ "shipping_address.phone": address?.phone || "",
64
+ }))
65
+
66
+ email &&
67
+ setFormData((prevState: Record<string, any>) => ({
68
+ ...prevState,
69
+ email: email,
70
+ }))
71
+ }
72
+
73
+ useEffect(() => {
74
+ // Ensure cart is not null and has a shipping_address before setting form data
75
+ if (cart && cart.shipping_address) {
76
+ setFormAddress(cart?.shipping_address, cart?.email)
77
+ }
78
+
79
+ if (cart && !cart.email && customer?.email) {
80
+ setFormAddress(undefined, customer.email)
81
+ }
82
+ }, [cart]) // Add cart as a dependency
83
+
84
+ const handleChange = (
85
+ e: React.ChangeEvent<
86
+ HTMLInputElement | HTMLInputElement | HTMLSelectElement
87
+ >
88
+ ) => {
89
+ setFormData({
90
+ ...formData,
91
+ [e.target.name]: e.target.value,
92
+ })
93
+ }
94
+
95
+ return (
96
+ <>
97
+ {customer && (addressesInRegion?.length || 0) > 0 && (
98
+ <Container className="mb-6 flex flex-col gap-y-4 p-5">
99
+ <p className="text-small-regular">
100
+ {`Hi ${customer.first_name}, do you want to use one of your saved addresses?`}
101
+ </p>
102
+ <AddressSelect
103
+ addresses={customer.addresses}
104
+ addressInput={
105
+ mapKeys(formData, (_, key) =>
106
+ key.replace("shipping_address.", "")
107
+ ) as HttpTypes.StoreCartAddress
108
+ }
109
+ onSelect={setFormAddress}
110
+ />
111
+ </Container>
112
+ )}
113
+ <div className="grid grid-cols-2 gap-4">
114
+ <Input
115
+ label="form.firstName"
116
+ name="shipping_address.first_name"
117
+ autoComplete="given-name"
118
+ value={formData["shipping_address.first_name"]}
119
+ onChange={handleChange}
120
+ required
121
+ data-testid="shipping-first-name-input"
122
+ />
123
+ <Input
124
+ label="form.lastName"
125
+ name="shipping_address.last_name"
126
+ autoComplete="family-name"
127
+ value={formData["shipping_address.last_name"]}
128
+ onChange={handleChange}
129
+ required
130
+ data-testid="shipping-last-name-input"
131
+ />
132
+ <Input
133
+ label="form.address"
134
+ name="shipping_address.address_1"
135
+ autoComplete="address-line1"
136
+ value={formData["shipping_address.address_1"]}
137
+ onChange={handleChange}
138
+ required
139
+ data-testid="shipping-address-input"
140
+ />
141
+ <Input
142
+ label="form.company"
143
+ name="shipping_address.company"
144
+ value={formData["shipping_address.company"]}
145
+ onChange={handleChange}
146
+ autoComplete="organization"
147
+ data-testid="shipping-company-input"
148
+ />
149
+ <Input
150
+ label="form.postalCode"
151
+ name="shipping_address.postal_code"
152
+ autoComplete="postal-code"
153
+ value={formData["shipping_address.postal_code"]}
154
+ onChange={handleChange}
155
+ required
156
+ data-testid="shipping-postal-code-input"
157
+ />
158
+ <Input
159
+ label="form.city"
160
+ name="shipping_address.city"
161
+ autoComplete="address-level2"
162
+ value={formData["shipping_address.city"]}
163
+ onChange={handleChange}
164
+ required
165
+ data-testid="shipping-city-input"
166
+ />
167
+ <CountrySelect
168
+ name="shipping_address.country_code"
169
+ autoComplete="country"
170
+ region={cart?.region}
171
+ value={formData["shipping_address.country_code"]}
172
+ onChange={handleChange}
173
+ required
174
+ data-testid="shipping-country-select"
175
+ />
176
+ <Input
177
+ label="form.province"
178
+ name="shipping_address.province"
179
+ autoComplete="address-level1"
180
+ value={formData["shipping_address.province"]}
181
+ onChange={handleChange}
182
+ data-testid="shipping-province-input"
183
+ />
184
+ </div>
185
+ <div className="my-8">
186
+ <Checkbox
187
+ label="checkout.billingSame"
188
+ name="same_as_billing"
189
+ checked={checked}
190
+ onChange={onChange}
191
+ data-testid="billing-address-checkbox"
192
+ />
193
+ </div>
194
+ <div className="grid grid-cols-2 gap-4 mb-4">
195
+ <Input
196
+ label="form.email"
197
+ name="email"
198
+ type="email"
199
+ title="form.emailInvalid"
200
+ autoComplete="email"
201
+ value={formData.email}
202
+ onChange={handleChange}
203
+ required
204
+ data-testid="shipping-email-input"
205
+ />
206
+ <Input
207
+ label="form.phone"
208
+ name="shipping_address.phone"
209
+ autoComplete="tel"
210
+ value={formData["shipping_address.phone"]}
211
+ onChange={handleChange}
212
+ data-testid="shipping-phone-input"
213
+ />
214
+ </div>
215
+ </>
216
+ )
217
+ }
218
+
219
+ export default ShippingAddress