@doswiftly/cli 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 (453) hide show
  1. package/README.md +357 -0
  2. package/bin/doswiftly.js +2 -0
  3. package/dist/commands/auth-github.d.ts +6 -0
  4. package/dist/commands/auth-github.d.ts.map +1 -0
  5. package/dist/commands/auth-github.js +89 -0
  6. package/dist/commands/auth-github.js.map +1 -0
  7. package/dist/commands/auth-token.d.ts +12 -0
  8. package/dist/commands/auth-token.d.ts.map +1 -0
  9. package/dist/commands/auth-token.js +43 -0
  10. package/dist/commands/auth-token.js.map +1 -0
  11. package/dist/commands/auth.d.ts +22 -0
  12. package/dist/commands/auth.d.ts.map +1 -0
  13. package/dist/commands/auth.js +348 -0
  14. package/dist/commands/auth.js.map +1 -0
  15. package/dist/commands/check.d.ts +5 -0
  16. package/dist/commands/check.d.ts.map +1 -0
  17. package/dist/commands/check.js +234 -0
  18. package/dist/commands/check.js.map +1 -0
  19. package/dist/commands/config.d.ts +3 -0
  20. package/dist/commands/config.d.ts.map +1 -0
  21. package/dist/commands/config.js +104 -0
  22. package/dist/commands/config.js.map +1 -0
  23. package/dist/commands/deploy.d.ts +37 -0
  24. package/dist/commands/deploy.d.ts.map +1 -0
  25. package/dist/commands/deploy.js +580 -0
  26. package/dist/commands/deploy.js.map +1 -0
  27. package/dist/commands/dev.d.ts +8 -0
  28. package/dist/commands/dev.d.ts.map +1 -0
  29. package/dist/commands/dev.js +83 -0
  30. package/dist/commands/dev.js.map +1 -0
  31. package/dist/commands/doctor.d.ts +5 -0
  32. package/dist/commands/doctor.d.ts.map +1 -0
  33. package/dist/commands/doctor.js +363 -0
  34. package/dist/commands/doctor.js.map +1 -0
  35. package/dist/commands/domain.d.ts +13 -0
  36. package/dist/commands/domain.d.ts.map +1 -0
  37. package/dist/commands/domain.js +128 -0
  38. package/dist/commands/domain.js.map +1 -0
  39. package/dist/commands/env.d.ts +25 -0
  40. package/dist/commands/env.d.ts.map +1 -0
  41. package/dist/commands/env.js +228 -0
  42. package/dist/commands/env.js.map +1 -0
  43. package/dist/commands/init.d.ts +11 -0
  44. package/dist/commands/init.d.ts.map +1 -0
  45. package/dist/commands/init.js +1028 -0
  46. package/dist/commands/init.js.map +1 -0
  47. package/dist/commands/inspect.d.ts +12 -0
  48. package/dist/commands/inspect.d.ts.map +1 -0
  49. package/dist/commands/inspect.js +162 -0
  50. package/dist/commands/inspect.js.map +1 -0
  51. package/dist/commands/migrate.d.ts +18 -0
  52. package/dist/commands/migrate.d.ts.map +1 -0
  53. package/dist/commands/migrate.js +355 -0
  54. package/dist/commands/migrate.js.map +1 -0
  55. package/dist/commands/preview.d.ts +29 -0
  56. package/dist/commands/preview.d.ts.map +1 -0
  57. package/dist/commands/preview.js +199 -0
  58. package/dist/commands/preview.js.map +1 -0
  59. package/dist/commands/proxy.d.ts +9 -0
  60. package/dist/commands/proxy.d.ts.map +1 -0
  61. package/dist/commands/proxy.js +37 -0
  62. package/dist/commands/proxy.js.map +1 -0
  63. package/dist/commands/sdk.d.ts +5 -0
  64. package/dist/commands/sdk.d.ts.map +1 -0
  65. package/dist/commands/sdk.js +82 -0
  66. package/dist/commands/sdk.js.map +1 -0
  67. package/dist/commands/template.d.ts +107 -0
  68. package/dist/commands/template.d.ts.map +1 -0
  69. package/dist/commands/template.js +1309 -0
  70. package/dist/commands/template.js.map +1 -0
  71. package/dist/commands/types.d.ts +5 -0
  72. package/dist/commands/types.d.ts.map +1 -0
  73. package/dist/commands/types.js +82 -0
  74. package/dist/commands/types.js.map +1 -0
  75. package/dist/commands/update.d.ts +2 -0
  76. package/dist/commands/update.d.ts.map +1 -0
  77. package/dist/commands/update.js +103 -0
  78. package/dist/commands/update.js.map +1 -0
  79. package/dist/commands/upgrade.d.ts +18 -0
  80. package/dist/commands/upgrade.d.ts.map +1 -0
  81. package/dist/commands/upgrade.js +55 -0
  82. package/dist/commands/upgrade.js.map +1 -0
  83. package/dist/commands/verify.d.ts +5 -0
  84. package/dist/commands/verify.d.ts.map +1 -0
  85. package/dist/commands/verify.js +232 -0
  86. package/dist/commands/verify.js.map +1 -0
  87. package/dist/commands/whoami.d.ts +5 -0
  88. package/dist/commands/whoami.d.ts.map +1 -0
  89. package/dist/commands/whoami.js +60 -0
  90. package/dist/commands/whoami.js.map +1 -0
  91. package/dist/config/types.d.ts +173 -0
  92. package/dist/config/types.d.ts.map +1 -0
  93. package/dist/config/types.js +48 -0
  94. package/dist/config/types.js.map +1 -0
  95. package/dist/index.d.ts +3 -0
  96. package/dist/index.d.ts.map +1 -0
  97. package/dist/index.js +416 -0
  98. package/dist/index.js.map +1 -0
  99. package/dist/lib/api-url.d.ts +14 -0
  100. package/dist/lib/api-url.d.ts.map +1 -0
  101. package/dist/lib/api-url.js +24 -0
  102. package/dist/lib/api-url.js.map +1 -0
  103. package/dist/lib/api.d.ts +67 -0
  104. package/dist/lib/api.d.ts.map +1 -0
  105. package/dist/lib/api.js +36 -0
  106. package/dist/lib/api.js.map +1 -0
  107. package/dist/lib/config.d.ts +39 -0
  108. package/dist/lib/config.d.ts.map +1 -0
  109. package/dist/lib/config.js +195 -0
  110. package/dist/lib/config.js.map +1 -0
  111. package/dist/lib/env-storage.d.ts +140 -0
  112. package/dist/lib/env-storage.d.ts.map +1 -0
  113. package/dist/lib/env-storage.js +464 -0
  114. package/dist/lib/env-storage.js.map +1 -0
  115. package/dist/lib/errors.d.ts +61 -0
  116. package/dist/lib/errors.d.ts.map +1 -0
  117. package/dist/lib/errors.js +204 -0
  118. package/dist/lib/errors.js.map +1 -0
  119. package/dist/lib/i18n.d.ts +99 -0
  120. package/dist/lib/i18n.d.ts.map +1 -0
  121. package/dist/lib/i18n.js +184 -0
  122. package/dist/lib/i18n.js.map +1 -0
  123. package/dist/lib/logger.d.ts +95 -0
  124. package/dist/lib/logger.d.ts.map +1 -0
  125. package/dist/lib/logger.js +168 -0
  126. package/dist/lib/logger.js.map +1 -0
  127. package/dist/lib/package-manager.d.ts +91 -0
  128. package/dist/lib/package-manager.d.ts.map +1 -0
  129. package/dist/lib/package-manager.js +205 -0
  130. package/dist/lib/package-manager.js.map +1 -0
  131. package/dist/lib/proxy-server.d.ts +24 -0
  132. package/dist/lib/proxy-server.d.ts.map +1 -0
  133. package/dist/lib/proxy-server.js +173 -0
  134. package/dist/lib/proxy-server.js.map +1 -0
  135. package/dist/lib/select-with-back.d.ts +34 -0
  136. package/dist/lib/select-with-back.d.ts.map +1 -0
  137. package/dist/lib/select-with-back.js +94 -0
  138. package/dist/lib/select-with-back.js.map +1 -0
  139. package/dist/lib/shared-api-client.d.ts +40 -0
  140. package/dist/lib/shared-api-client.d.ts.map +1 -0
  141. package/dist/lib/shared-api-client.js +92 -0
  142. package/dist/lib/shared-api-client.js.map +1 -0
  143. package/dist/lib/wizard-engine.d.ts +128 -0
  144. package/dist/lib/wizard-engine.d.ts.map +1 -0
  145. package/dist/lib/wizard-engine.js +168 -0
  146. package/dist/lib/wizard-engine.js.map +1 -0
  147. package/package.json +85 -0
  148. package/templates/storefront-minimal/.env.example +10 -0
  149. package/templates/storefront-minimal/.github/workflows/build-template.yml +109 -0
  150. package/templates/storefront-minimal/app/globals.css +18 -0
  151. package/templates/storefront-minimal/app/layout.tsx +26 -0
  152. package/templates/storefront-minimal/app/page.tsx +93 -0
  153. package/templates/storefront-minimal/lib/graphql-client.ts +23 -0
  154. package/templates/storefront-minimal/next.config.ts +15 -0
  155. package/templates/storefront-minimal/open-next.config.ts +3 -0
  156. package/templates/storefront-minimal/package.json +30 -0
  157. package/templates/storefront-minimal/postcss.config.mjs +5 -0
  158. package/templates/storefront-minimal/tailwind.config.ts +14 -0
  159. package/templates/storefront-minimal/tsconfig.json +27 -0
  160. package/templates/storefront-minimal/wrangler.toml +9 -0
  161. package/templates/storefront-nextjs/.env.example +68 -0
  162. package/templates/storefront-nextjs/.github/workflows/build-template.yml +109 -0
  163. package/templates/storefront-nextjs/.github/workflows/deploy.yml +25 -0
  164. package/templates/storefront-nextjs/.github/workflows/preview.yml +22 -0
  165. package/templates/storefront-nextjs/README.md +520 -0
  166. package/templates/storefront-nextjs/app/account/orders/page.tsx +216 -0
  167. package/templates/storefront-nextjs/app/account/page.tsx +167 -0
  168. package/templates/storefront-nextjs/app/auth/login/page.tsx +135 -0
  169. package/templates/storefront-nextjs/app/auth/register/page.tsx +228 -0
  170. package/templates/storefront-nextjs/app/cart/page.tsx +263 -0
  171. package/templates/storefront-nextjs/app/categories/[slug]/page.tsx +200 -0
  172. package/templates/storefront-nextjs/app/categories/page.tsx +58 -0
  173. package/templates/storefront-nextjs/app/checkout/page.tsx +351 -0
  174. package/templates/storefront-nextjs/app/collections/[slug]/page.tsx +158 -0
  175. package/templates/storefront-nextjs/app/collections/page.tsx +61 -0
  176. package/templates/storefront-nextjs/app/globals.css +98 -0
  177. package/templates/storefront-nextjs/app/layout.tsx +39 -0
  178. package/templates/storefront-nextjs/app/page.tsx +136 -0
  179. package/templates/storefront-nextjs/app/products/[slug]/page.tsx +119 -0
  180. package/templates/storefront-nextjs/app/products/page.tsx +107 -0
  181. package/templates/storefront-nextjs/app/search/page.tsx +127 -0
  182. package/templates/storefront-nextjs/components/auth/auth-guard.tsx +94 -0
  183. package/templates/storefront-nextjs/components/commerce/add-to-cart-button.tsx +77 -0
  184. package/templates/storefront-nextjs/components/commerce/cart-icon.tsx +29 -0
  185. package/templates/storefront-nextjs/components/commerce/currency-selector.tsx +217 -0
  186. package/templates/storefront-nextjs/components/commerce/pagination.tsx +62 -0
  187. package/templates/storefront-nextjs/components/commerce/product-actions.tsx +135 -0
  188. package/templates/storefront-nextjs/components/commerce/product-filters.tsx +109 -0
  189. package/templates/storefront-nextjs/components/commerce/product-price.tsx +375 -0
  190. package/templates/storefront-nextjs/components/commerce/search-input.tsx +178 -0
  191. package/templates/storefront-nextjs/components/commerce/sort-select.tsx +64 -0
  192. package/templates/storefront-nextjs/components/commerce/variant-selector.tsx +210 -0
  193. package/templates/storefront-nextjs/components/layout/footer.tsx +107 -0
  194. package/templates/storefront-nextjs/components/layout/header.tsx +104 -0
  195. package/templates/storefront-nextjs/components/providers.tsx +62 -0
  196. package/templates/storefront-nextjs/lib/auth/routes.ts +52 -0
  197. package/templates/storefront-nextjs/lib/currency.tsx +140 -0
  198. package/templates/storefront-nextjs/lib/format.ts +159 -0
  199. package/templates/storefront-nextjs/lib/graphql-queries.ts +629 -0
  200. package/templates/storefront-nextjs/lib/hooks.ts +30 -0
  201. package/templates/storefront-nextjs/middleware.ts +80 -0
  202. package/templates/storefront-nextjs/next.config.ts +37 -0
  203. package/templates/storefront-nextjs/open-next.config.ts +3 -0
  204. package/templates/storefront-nextjs/package.dev.json +30 -0
  205. package/templates/storefront-nextjs/package.json +32 -0
  206. package/templates/storefront-nextjs/package.json.template +32 -0
  207. package/templates/storefront-nextjs/postcss.config.mjs +8 -0
  208. package/templates/storefront-nextjs/tailwind.config.ts +111 -0
  209. package/templates/storefront-nextjs/tsconfig.json +27 -0
  210. package/templates/storefront-nextjs/wrangler.toml +9 -0
  211. package/templates/storefront-nextjs-shadcn/.env.example +68 -0
  212. package/templates/storefront-nextjs-shadcn/.github/workflows/build-template.yml +109 -0
  213. package/templates/storefront-nextjs-shadcn/.github/workflows/deploy.yml +25 -0
  214. package/templates/storefront-nextjs-shadcn/.github/workflows/preview.yml +22 -0
  215. package/templates/storefront-nextjs-shadcn/CART_INTEGRATION.md +282 -0
  216. package/templates/storefront-nextjs-shadcn/CLAUDE.md +96 -0
  217. package/templates/storefront-nextjs-shadcn/GRAPHQL_DOCUMENT_NAMES.md +190 -0
  218. package/templates/storefront-nextjs-shadcn/GRAPHQL_ERROR_HANDLING.md +263 -0
  219. package/templates/storefront-nextjs-shadcn/GRAPHQL_FIXES_SUMMARY.md +135 -0
  220. package/templates/storefront-nextjs-shadcn/GRAPHQL_INTEGRATION_COMPLETE.md +142 -0
  221. package/templates/storefront-nextjs-shadcn/INTEGRATION_CHECKLIST.md +448 -0
  222. package/templates/storefront-nextjs-shadcn/PRODUCT_DETAIL_PAGE_IMPLEMENTATION.md +307 -0
  223. package/templates/storefront-nextjs-shadcn/README.md +195 -0
  224. package/templates/storefront-nextjs-shadcn/THEME_CUSTOMIZATION.md +245 -0
  225. package/templates/storefront-nextjs-shadcn/app/about/page.tsx +34 -0
  226. package/templates/storefront-nextjs-shadcn/app/account/addresses/page.tsx +215 -0
  227. package/templates/storefront-nextjs-shadcn/app/account/loyalty/page.tsx +484 -0
  228. package/templates/storefront-nextjs-shadcn/app/account/orders/[id]/page.tsx +128 -0
  229. package/templates/storefront-nextjs-shadcn/app/account/orders/[id]/tracking/page.tsx +206 -0
  230. package/templates/storefront-nextjs-shadcn/app/account/orders/page.tsx +80 -0
  231. package/templates/storefront-nextjs-shadcn/app/account/page.tsx +107 -0
  232. package/templates/storefront-nextjs-shadcn/app/account/settings/page.tsx +195 -0
  233. package/templates/storefront-nextjs-shadcn/app/api/auth/clear-token/route.ts +87 -0
  234. package/templates/storefront-nextjs-shadcn/app/api/auth/set-token/route.ts +125 -0
  235. package/templates/storefront-nextjs-shadcn/app/auth/forgot-password/page.tsx +131 -0
  236. package/templates/storefront-nextjs-shadcn/app/auth/login/page.tsx +24 -0
  237. package/templates/storefront-nextjs-shadcn/app/auth/register/page.tsx +20 -0
  238. package/templates/storefront-nextjs-shadcn/app/blog/[slug]/page.tsx +323 -0
  239. package/templates/storefront-nextjs-shadcn/app/blog/page.tsx +159 -0
  240. package/templates/storefront-nextjs-shadcn/app/brands/[slug]/page.tsx +170 -0
  241. package/templates/storefront-nextjs-shadcn/app/brands/page.tsx +73 -0
  242. package/templates/storefront-nextjs-shadcn/app/cart/page.tsx +165 -0
  243. package/templates/storefront-nextjs-shadcn/app/categories/[slug]/page.tsx +78 -0
  244. package/templates/storefront-nextjs-shadcn/app/categories/page.tsx +75 -0
  245. package/templates/storefront-nextjs-shadcn/app/checkout/page.tsx +1752 -0
  246. package/templates/storefront-nextjs-shadcn/app/checkout/success/[orderId]/page.tsx +256 -0
  247. package/templates/storefront-nextjs-shadcn/app/collections/[handle]/page.tsx +74 -0
  248. package/templates/storefront-nextjs-shadcn/app/collections/page.tsx +75 -0
  249. package/templates/storefront-nextjs-shadcn/app/contact/page.tsx +114 -0
  250. package/templates/storefront-nextjs-shadcn/app/error.tsx +90 -0
  251. package/templates/storefront-nextjs-shadcn/app/globals.css +125 -0
  252. package/templates/storefront-nextjs-shadcn/app/layout.tsx +57 -0
  253. package/templates/storefront-nextjs-shadcn/app/not-found.tsx +68 -0
  254. package/templates/storefront-nextjs-shadcn/app/page.tsx +21 -0
  255. package/templates/storefront-nextjs-shadcn/app/products/[slug]/page.tsx +246 -0
  256. package/templates/storefront-nextjs-shadcn/app/products/[slug]/product-client.tsx +343 -0
  257. package/templates/storefront-nextjs-shadcn/app/products/page.tsx +25 -0
  258. package/templates/storefront-nextjs-shadcn/app/products/products-client.tsx +192 -0
  259. package/templates/storefront-nextjs-shadcn/app/returns/page.tsx +77 -0
  260. package/templates/storefront-nextjs-shadcn/app/robots.ts +53 -0
  261. package/templates/storefront-nextjs-shadcn/app/search/page.tsx +16 -0
  262. package/templates/storefront-nextjs-shadcn/app/search/search-client.tsx +47 -0
  263. package/templates/storefront-nextjs-shadcn/app/shipping/page.tsx +62 -0
  264. package/templates/storefront-nextjs-shadcn/app/sitemap.ts +144 -0
  265. package/templates/storefront-nextjs-shadcn/app/wishlist/page.tsx +179 -0
  266. package/templates/storefront-nextjs-shadcn/codegen.ts +51 -0
  267. package/templates/storefront-nextjs-shadcn/components/account/address-form.tsx +348 -0
  268. package/templates/storefront-nextjs-shadcn/components/account/address-list.tsx +144 -0
  269. package/templates/storefront-nextjs-shadcn/components/account/order-details.tsx +258 -0
  270. package/templates/storefront-nextjs-shadcn/components/account/order-history.tsx +107 -0
  271. package/templates/storefront-nextjs-shadcn/components/auth/account-menu.tsx +132 -0
  272. package/templates/storefront-nextjs-shadcn/components/auth/login-form.tsx +188 -0
  273. package/templates/storefront-nextjs-shadcn/components/auth/register-form.tsx +305 -0
  274. package/templates/storefront-nextjs-shadcn/components/blog/blog-card.tsx +240 -0
  275. package/templates/storefront-nextjs-shadcn/components/blog/blog-sidebar.tsx +177 -0
  276. package/templates/storefront-nextjs-shadcn/components/blog/index.ts +8 -0
  277. package/templates/storefront-nextjs-shadcn/components/brand/brand-card.tsx +119 -0
  278. package/templates/storefront-nextjs-shadcn/components/brand/brand-grid.tsx +64 -0
  279. package/templates/storefront-nextjs-shadcn/components/cart/cart-drawer.tsx +140 -0
  280. package/templates/storefront-nextjs-shadcn/components/cart/cart-icon.tsx +48 -0
  281. package/templates/storefront-nextjs-shadcn/components/cart/cart-item.tsx +112 -0
  282. package/templates/storefront-nextjs-shadcn/components/cart/cart-summary.tsx +84 -0
  283. package/templates/storefront-nextjs-shadcn/components/cart/index.ts +17 -0
  284. package/templates/storefront-nextjs-shadcn/components/cart/promo-code-input.tsx +121 -0
  285. package/templates/storefront-nextjs-shadcn/components/cart/shipping-estimator.tsx +162 -0
  286. package/templates/storefront-nextjs-shadcn/components/checkout/index.ts +25 -0
  287. package/templates/storefront-nextjs-shadcn/components/checkout/payment-method-card.tsx +187 -0
  288. package/templates/storefront-nextjs-shadcn/components/checkout/payment-step.tsx +160 -0
  289. package/templates/storefront-nextjs-shadcn/components/checkout/tax-breakdown.tsx +154 -0
  290. package/templates/storefront-nextjs-shadcn/components/commerce/currency-selector.tsx +225 -0
  291. package/templates/storefront-nextjs-shadcn/components/commerce/pagination.tsx +62 -0
  292. package/templates/storefront-nextjs-shadcn/components/commerce/product-actions.tsx +158 -0
  293. package/templates/storefront-nextjs-shadcn/components/commerce/search-input.tsx +174 -0
  294. package/templates/storefront-nextjs-shadcn/components/commerce/variant-selector.tsx +210 -0
  295. package/templates/storefront-nextjs-shadcn/components/common/category-card.tsx +97 -0
  296. package/templates/storefront-nextjs-shadcn/components/common/collection-card.tsx +187 -0
  297. package/templates/storefront-nextjs-shadcn/components/common/price-display.tsx +151 -0
  298. package/templates/storefront-nextjs-shadcn/components/common/social-share.tsx +166 -0
  299. package/templates/storefront-nextjs-shadcn/components/discount/discount-breakdown.tsx +245 -0
  300. package/templates/storefront-nextjs-shadcn/components/discount/discount-code-input.tsx +246 -0
  301. package/templates/storefront-nextjs-shadcn/components/discount/index.ts +19 -0
  302. package/templates/storefront-nextjs-shadcn/components/error/error-boundary.tsx +113 -0
  303. package/templates/storefront-nextjs-shadcn/components/error/index.ts +7 -0
  304. package/templates/storefront-nextjs-shadcn/components/filters/attribute-filter.tsx +153 -0
  305. package/templates/storefront-nextjs-shadcn/components/filters/checkbox-group-filter.tsx +167 -0
  306. package/templates/storefront-nextjs-shadcn/components/filters/color-swatch-filter.tsx +176 -0
  307. package/templates/storefront-nextjs-shadcn/components/filters/dynamic-attribute-filters.tsx +220 -0
  308. package/templates/storefront-nextjs-shadcn/components/filters/index.ts +36 -0
  309. package/templates/storefront-nextjs-shadcn/components/filters/range-slider-filter.tsx +193 -0
  310. package/templates/storefront-nextjs-shadcn/components/filters/toggle-filter.tsx +132 -0
  311. package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-balance.tsx +321 -0
  312. package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-input.tsx +309 -0
  313. package/templates/storefront-nextjs-shadcn/components/gift-card/index.ts +24 -0
  314. package/templates/storefront-nextjs-shadcn/components/home/category-grid.tsx +72 -0
  315. package/templates/storefront-nextjs-shadcn/components/home/featured-collections.tsx +107 -0
  316. package/templates/storefront-nextjs-shadcn/components/home/featured-products.tsx +85 -0
  317. package/templates/storefront-nextjs-shadcn/components/home/hero-section.tsx +34 -0
  318. package/templates/storefront-nextjs-shadcn/components/home/index.ts +8 -0
  319. package/templates/storefront-nextjs-shadcn/components/home/newsletter-signup.tsx +108 -0
  320. package/templates/storefront-nextjs-shadcn/components/layout/breadcrumbs.tsx +133 -0
  321. package/templates/storefront-nextjs-shadcn/components/layout/currency-selector.tsx +341 -0
  322. package/templates/storefront-nextjs-shadcn/components/layout/footer.tsx +128 -0
  323. package/templates/storefront-nextjs-shadcn/components/layout/header.tsx +147 -0
  324. package/templates/storefront-nextjs-shadcn/components/layout/index.ts +9 -0
  325. package/templates/storefront-nextjs-shadcn/components/layout/mobile-menu.tsx +211 -0
  326. package/templates/storefront-nextjs-shadcn/components/layout/navigation.tsx +95 -0
  327. package/templates/storefront-nextjs-shadcn/components/layout/theme-switcher.tsx +192 -0
  328. package/templates/storefront-nextjs-shadcn/components/loyalty/index.ts +11 -0
  329. package/templates/storefront-nextjs-shadcn/components/loyalty/points-balance.tsx +93 -0
  330. package/templates/storefront-nextjs-shadcn/components/loyalty/points-history.tsx +177 -0
  331. package/templates/storefront-nextjs-shadcn/components/loyalty/referral-section.tsx +250 -0
  332. package/templates/storefront-nextjs-shadcn/components/loyalty/rewards-catalog.tsx +217 -0
  333. package/templates/storefront-nextjs-shadcn/components/loyalty/tier-badge.tsx +106 -0
  334. package/templates/storefront-nextjs-shadcn/components/loyalty/tier-progress.tsx +131 -0
  335. package/templates/storefront-nextjs-shadcn/components/order/delivery-estimate.tsx +196 -0
  336. package/templates/storefront-nextjs-shadcn/components/order/index.ts +11 -0
  337. package/templates/storefront-nextjs-shadcn/components/order/order-tracking.tsx +200 -0
  338. package/templates/storefront-nextjs-shadcn/components/order/shipment-card.tsx +407 -0
  339. package/templates/storefront-nextjs-shadcn/components/order/tracking-status.tsx +222 -0
  340. package/templates/storefront-nextjs-shadcn/components/order/tracking-timeline.tsx +205 -0
  341. package/templates/storefront-nextjs-shadcn/components/product/add-to-cart-button.tsx +161 -0
  342. package/templates/storefront-nextjs-shadcn/components/product/b2b-price-display.tsx +250 -0
  343. package/templates/storefront-nextjs-shadcn/components/product/discount-badge.tsx +196 -0
  344. package/templates/storefront-nextjs-shadcn/components/product/index.ts +41 -0
  345. package/templates/storefront-nextjs-shadcn/components/product/product-card.tsx +147 -0
  346. package/templates/storefront-nextjs-shadcn/components/product/product-filters.tsx +217 -0
  347. package/templates/storefront-nextjs-shadcn/components/product/product-gallery.tsx +143 -0
  348. package/templates/storefront-nextjs-shadcn/components/product/product-grid.tsx +83 -0
  349. package/templates/storefront-nextjs-shadcn/components/product/product-image.tsx +155 -0
  350. package/templates/storefront-nextjs-shadcn/components/product/product-price.tsx +158 -0
  351. package/templates/storefront-nextjs-shadcn/components/product/product-quantity-selector.tsx +111 -0
  352. package/templates/storefront-nextjs-shadcn/components/product/product-reviews.tsx +238 -0
  353. package/templates/storefront-nextjs-shadcn/components/product/product-sort.tsx +58 -0
  354. package/templates/storefront-nextjs-shadcn/components/product/product-variant-selector.tsx +169 -0
  355. package/templates/storefront-nextjs-shadcn/components/product/review-card.tsx +220 -0
  356. package/templates/storefront-nextjs-shadcn/components/product/review-form.tsx +338 -0
  357. package/templates/storefront-nextjs-shadcn/components/product/review-summary.tsx +143 -0
  358. package/templates/storefront-nextjs-shadcn/components/product/sale-countdown.tsx +166 -0
  359. package/templates/storefront-nextjs-shadcn/components/product/savings-display.tsx +213 -0
  360. package/templates/storefront-nextjs-shadcn/components/product/similar-products.tsx +57 -0
  361. package/templates/storefront-nextjs-shadcn/components/product/stock-indicator.tsx +91 -0
  362. package/templates/storefront-nextjs-shadcn/components/providers/currency-provider.tsx +103 -0
  363. package/templates/storefront-nextjs-shadcn/components/providers/index.ts +8 -0
  364. package/templates/storefront-nextjs-shadcn/components/providers/query-provider.tsx +260 -0
  365. package/templates/storefront-nextjs-shadcn/components/providers/theme-provider.tsx +13 -0
  366. package/templates/storefront-nextjs-shadcn/components/returns/index.ts +26 -0
  367. package/templates/storefront-nextjs-shadcn/components/returns/return-request-form.tsx +608 -0
  368. package/templates/storefront-nextjs-shadcn/components/returns/return-status-card.tsx +554 -0
  369. package/templates/storefront-nextjs-shadcn/components/search/index.ts +8 -0
  370. package/templates/storefront-nextjs-shadcn/components/search/search-bar.tsx +140 -0
  371. package/templates/storefront-nextjs-shadcn/components/search/search-results.tsx +58 -0
  372. package/templates/storefront-nextjs-shadcn/components/search/search-suggestions.tsx +43 -0
  373. package/templates/storefront-nextjs-shadcn/components/seo/index.ts +12 -0
  374. package/templates/storefront-nextjs-shadcn/components/seo/json-ld.tsx +56 -0
  375. package/templates/storefront-nextjs-shadcn/components/seo/product-json-ld.ts +167 -0
  376. package/templates/storefront-nextjs-shadcn/components/shipping/index.ts +16 -0
  377. package/templates/storefront-nextjs-shadcn/components/shipping/shipping-method-selector.tsx +337 -0
  378. package/templates/storefront-nextjs-shadcn/components/ui/accordion.tsx +153 -0
  379. package/templates/storefront-nextjs-shadcn/components/ui/alert.tsx +59 -0
  380. package/templates/storefront-nextjs-shadcn/components/ui/badge.tsx +34 -0
  381. package/templates/storefront-nextjs-shadcn/components/ui/button.tsx +51 -0
  382. package/templates/storefront-nextjs-shadcn/components/ui/card.tsx +77 -0
  383. package/templates/storefront-nextjs-shadcn/components/ui/checkbox.tsx +30 -0
  384. package/templates/storefront-nextjs-shadcn/components/ui/dialog.tsx +137 -0
  385. package/templates/storefront-nextjs-shadcn/components/ui/empty-state.tsx +207 -0
  386. package/templates/storefront-nextjs-shadcn/components/ui/index.ts +67 -0
  387. package/templates/storefront-nextjs-shadcn/components/ui/input.tsx +65 -0
  388. package/templates/storefront-nextjs-shadcn/components/ui/label.tsx +26 -0
  389. package/templates/storefront-nextjs-shadcn/components/ui/pagination.tsx +205 -0
  390. package/templates/storefront-nextjs-shadcn/components/ui/radio-group.tsx +44 -0
  391. package/templates/storefront-nextjs-shadcn/components/ui/select.tsx +160 -0
  392. package/templates/storefront-nextjs-shadcn/components/ui/separator.tsx +28 -0
  393. package/templates/storefront-nextjs-shadcn/components/ui/skeleton.tsx +20 -0
  394. package/templates/storefront-nextjs-shadcn/components/ui/spinner.tsx +82 -0
  395. package/templates/storefront-nextjs-shadcn/components/ui/tabs.tsx +119 -0
  396. package/templates/storefront-nextjs-shadcn/components/ui/toast.tsx +96 -0
  397. package/templates/storefront-nextjs-shadcn/components/wishlist/index.ts +9 -0
  398. package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-button.tsx +148 -0
  399. package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-icon.tsx +47 -0
  400. package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-item.tsx +165 -0
  401. package/templates/storefront-nextjs-shadcn/components.json +19 -0
  402. package/templates/storefront-nextjs-shadcn/generated/.gitkeep +2 -0
  403. package/templates/storefront-nextjs-shadcn/graphql/.gitkeep +31 -0
  404. package/templates/storefront-nextjs-shadcn/graphql/collections.example.ts +168 -0
  405. package/templates/storefront-nextjs-shadcn/graphql/products.example.ts +160 -0
  406. package/templates/storefront-nextjs-shadcn/hooks/index.ts +9 -0
  407. package/templates/storefront-nextjs-shadcn/hooks/use-auth.ts +310 -0
  408. package/templates/storefront-nextjs-shadcn/hooks/use-cart-actions.ts +286 -0
  409. package/templates/storefront-nextjs-shadcn/hooks/use-cart-sync.ts +110 -0
  410. package/templates/storefront-nextjs-shadcn/hooks/use-filter-params.test.ts +173 -0
  411. package/templates/storefront-nextjs-shadcn/hooks/use-filter-params.ts +298 -0
  412. package/templates/storefront-nextjs-shadcn/lib/auth/cookies.ts +220 -0
  413. package/templates/storefront-nextjs-shadcn/lib/auth/routes.ts +57 -0
  414. package/templates/storefront-nextjs-shadcn/lib/config.ts +46 -0
  415. package/templates/storefront-nextjs-shadcn/lib/currency/IMPLEMENTATION_SUMMARY.md +254 -0
  416. package/templates/storefront-nextjs-shadcn/lib/currency/README.md +464 -0
  417. package/templates/storefront-nextjs-shadcn/lib/currency/cookie-manager.test.ts +328 -0
  418. package/templates/storefront-nextjs-shadcn/lib/currency/cookie-manager.ts +295 -0
  419. package/templates/storefront-nextjs-shadcn/lib/currency/index.ts +27 -0
  420. package/templates/storefront-nextjs-shadcn/lib/format.test.ts +397 -0
  421. package/templates/storefront-nextjs-shadcn/lib/format.ts +226 -0
  422. package/templates/storefront-nextjs-shadcn/lib/graphql/client.ts +109 -0
  423. package/templates/storefront-nextjs-shadcn/lib/graphql/hooks.ts +1183 -0
  424. package/templates/storefront-nextjs-shadcn/lib/graphql/server.ts +267 -0
  425. package/templates/storefront-nextjs-shadcn/lib/hooks.ts +30 -0
  426. package/templates/storefront-nextjs-shadcn/lib/theme/theme-config.ts +89 -0
  427. package/templates/storefront-nextjs-shadcn/lib/utils.ts +6 -0
  428. package/templates/storefront-nextjs-shadcn/next.config.ts +47 -0
  429. package/templates/storefront-nextjs-shadcn/open-next.config.ts +3 -0
  430. package/templates/storefront-nextjs-shadcn/package.dev.json +30 -0
  431. package/templates/storefront-nextjs-shadcn/package.json +60 -0
  432. package/templates/storefront-nextjs-shadcn/package.json.template +46 -0
  433. package/templates/storefront-nextjs-shadcn/postcss.config.mjs +8 -0
  434. package/templates/storefront-nextjs-shadcn/proxy.ts +80 -0
  435. package/templates/storefront-nextjs-shadcn/public/icons/payment/apple-pay.svg +8 -0
  436. package/templates/storefront-nextjs-shadcn/public/icons/payment/bank-transfer.svg +10 -0
  437. package/templates/storefront-nextjs-shadcn/public/icons/payment/blik.svg +6 -0
  438. package/templates/storefront-nextjs-shadcn/public/icons/payment/cash-on-delivery.svg +11 -0
  439. package/templates/storefront-nextjs-shadcn/public/icons/payment/google-pay.svg +11 -0
  440. package/templates/storefront-nextjs-shadcn/public/icons/payment/mastercard.svg +7 -0
  441. package/templates/storefront-nextjs-shadcn/public/icons/payment/paypal.svg +7 -0
  442. package/templates/storefront-nextjs-shadcn/public/icons/payment/payu.svg +7 -0
  443. package/templates/storefront-nextjs-shadcn/public/icons/payment/przelewy24.svg +7 -0
  444. package/templates/storefront-nextjs-shadcn/public/icons/payment/stripe.svg +4 -0
  445. package/templates/storefront-nextjs-shadcn/public/icons/payment/visa.svg +5 -0
  446. package/templates/storefront-nextjs-shadcn/stores/auth-store.ts +66 -0
  447. package/templates/storefront-nextjs-shadcn/stores/cart-store.ts +56 -0
  448. package/templates/storefront-nextjs-shadcn/stores/checkout-store.ts +184 -0
  449. package/templates/storefront-nextjs-shadcn/stores/currency-store.ts +103 -0
  450. package/templates/storefront-nextjs-shadcn/stores/wishlist-store.ts +291 -0
  451. package/templates/storefront-nextjs-shadcn/tailwind.config.ts +111 -0
  452. package/templates/storefront-nextjs-shadcn/tsconfig.json +27 -0
  453. package/templates/storefront-nextjs-shadcn/wrangler.toml +9 -0
@@ -0,0 +1,1752 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect } from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import Link from "next/link";
6
+ import { useCartStore } from "@/stores/cart-store";
7
+ import { useCartSync } from "@/hooks/use-cart-sync";
8
+ import {
9
+ useCheckoutCreate,
10
+ useCheckoutEmailUpdate,
11
+ useCheckoutShippingAddressUpdate,
12
+ useCheckoutBillingAddressUpdate,
13
+ useCheckoutShippingLineUpdate,
14
+ useCheckoutDiscountCodeApply,
15
+ useCheckoutDiscountCodeRemove,
16
+ useCheckoutGiftCardApply,
17
+ useCheckoutGiftCardRemove,
18
+ useCheckoutComplete,
19
+ useCheckout,
20
+ } from "@/lib/graphql/hooks";
21
+ import { formatAmount } from "@/lib/format";
22
+ import { Button } from "@/components/ui/button";
23
+ import { Input } from "@/components/ui/input";
24
+ import { Label } from "@/components/ui/label";
25
+ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
26
+ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
27
+ import { Checkbox } from "@/components/ui/checkbox";
28
+ import {
29
+ Select,
30
+ SelectContent,
31
+ SelectItem,
32
+ SelectTrigger,
33
+ SelectValue,
34
+ } from "@/components/ui/select";
35
+ import { Breadcrumbs } from "@/components/layout/breadcrumbs";
36
+ import { PaymentStep } from "@/components/checkout/payment-step";
37
+ import type { PaymentMethod } from "@/components/checkout/payment-method-card";
38
+ import { toast } from "sonner";
39
+ import { z } from "zod";
40
+ import {
41
+ Loader2,
42
+ ChevronLeft,
43
+ ChevronRight,
44
+ Check,
45
+ Package,
46
+ Truck,
47
+ CreditCard,
48
+ ClipboardCheck,
49
+ Tag,
50
+ X,
51
+ User,
52
+ ShoppingBag,
53
+ Gift,
54
+ } from "lucide-react";
55
+
56
+ // ============================================================================
57
+ // CONSTANTS
58
+ // ============================================================================
59
+
60
+ const COUNTRIES = [
61
+ { code: "PL", name: "Polska" },
62
+ { code: "DE", name: "Niemcy" },
63
+ { code: "CZ", name: "Czechy" },
64
+ { code: "SK", name: "Słowacja" },
65
+ { code: "AT", name: "Austria" },
66
+ { code: "FR", name: "Francja" },
67
+ { code: "NL", name: "Holandia" },
68
+ { code: "BE", name: "Belgia" },
69
+ { code: "IT", name: "Włochy" },
70
+ { code: "ES", name: "Hiszpania" },
71
+ { code: "GB", name: "Wielka Brytania" },
72
+ { code: "US", name: "Stany Zjednoczone" },
73
+ ];
74
+
75
+ // ============================================================================
76
+ // VALIDATION SCHEMAS
77
+ // ============================================================================
78
+
79
+ const contactSchema = z.object({
80
+ email: z.string().email("Podaj prawidłowy adres email"),
81
+ phone: z
82
+ .string()
83
+ .min(9, "Numer telefonu jest wymagany")
84
+ .regex(/^\+?[0-9\s\-]{9,15}$/, "Nieprawidłowy format numeru telefonu"),
85
+ });
86
+
87
+ const addressSchema = z.object({
88
+ firstName: z.string().min(1, "Imię jest wymagane"),
89
+ lastName: z.string().min(1, "Nazwisko jest wymagane"),
90
+ address1: z.string().min(1, "Adres jest wymagany"),
91
+ address2: z.string().optional(),
92
+ city: z.string().min(1, "Miasto jest wymagane"),
93
+ province: z.string().optional(),
94
+ zip: z.string().min(1, "Kod pocztowy jest wymagany"),
95
+ country: z.string().min(2, "Kraj jest wymagany"),
96
+ phone: z.string().optional(),
97
+ company: z.string().optional(),
98
+ });
99
+
100
+ // ============================================================================
101
+ // TYPES
102
+ // ============================================================================
103
+
104
+ type CheckoutStep = "contact" | "shipping" | "delivery" | "payment" | "review";
105
+
106
+ interface ShippingRate {
107
+ handle: string;
108
+ title: string;
109
+ price: {
110
+ amount: string;
111
+ currencyCode: string;
112
+ };
113
+ }
114
+
115
+ interface AddressForm {
116
+ firstName: string;
117
+ lastName: string;
118
+ address1: string;
119
+ address2: string;
120
+ city: string;
121
+ province: string;
122
+ zip: string;
123
+ country: string;
124
+ phone: string;
125
+ company: string;
126
+ }
127
+
128
+ interface CheckoutFormState {
129
+ email: string;
130
+ phone: string;
131
+ shippingAddress: AddressForm;
132
+ billingAddress: AddressForm;
133
+ sameAsBilling: boolean;
134
+ selectedShippingRate: string | null;
135
+ selectedPaymentMethodId: string | null;
136
+ discountCode: string;
137
+ appliedDiscountCode: string | null;
138
+ giftCardCode: string;
139
+ acceptTerms: boolean;
140
+ acceptMarketing: boolean;
141
+ }
142
+
143
+ const emptyAddress: AddressForm = {
144
+ firstName: "",
145
+ lastName: "",
146
+ address1: "",
147
+ address2: "",
148
+ city: "",
149
+ province: "",
150
+ zip: "",
151
+ country: "PL",
152
+ phone: "",
153
+ company: "",
154
+ };
155
+
156
+ // ============================================================================
157
+ // ADDRESS FORM FIELDS COMPONENT (extracted to prevent re-mount on state change)
158
+ // ============================================================================
159
+
160
+ interface AddressFormFieldsProps {
161
+ prefix: "shipping" | "billing";
162
+ values: AddressForm;
163
+ onChange: (field: keyof AddressForm, value: string) => void;
164
+ errors: Record<string, string>;
165
+ }
166
+
167
+ function AddressFormFields({ prefix, values, onChange, errors }: AddressFormFieldsProps) {
168
+ return (
169
+ <div className="space-y-4">
170
+ <div className="grid gap-4 sm:grid-cols-2">
171
+ <div>
172
+ <Label htmlFor={`${prefix}_firstName`}>Imię *</Label>
173
+ <Input
174
+ id={`${prefix}_firstName`}
175
+ value={values.firstName}
176
+ onChange={(e) => onChange("firstName", e.target.value)}
177
+ className={errors[`${prefix}_firstName`] ? "border-destructive" : ""}
178
+ />
179
+ {errors[`${prefix}_firstName`] && (
180
+ <p className="mt-1 text-sm text-destructive">{errors[`${prefix}_firstName`]}</p>
181
+ )}
182
+ </div>
183
+ <div>
184
+ <Label htmlFor={`${prefix}_lastName`}>Nazwisko *</Label>
185
+ <Input
186
+ id={`${prefix}_lastName`}
187
+ value={values.lastName}
188
+ onChange={(e) => onChange("lastName", e.target.value)}
189
+ className={errors[`${prefix}_lastName`] ? "border-destructive" : ""}
190
+ />
191
+ {errors[`${prefix}_lastName`] && (
192
+ <p className="mt-1 text-sm text-destructive">{errors[`${prefix}_lastName`]}</p>
193
+ )}
194
+ </div>
195
+ </div>
196
+
197
+ <div>
198
+ <Label htmlFor={`${prefix}_company`}>Firma (opcjonalnie)</Label>
199
+ <Input
200
+ id={`${prefix}_company`}
201
+ value={values.company}
202
+ onChange={(e) => onChange("company", e.target.value)}
203
+ />
204
+ </div>
205
+
206
+ <div>
207
+ <Label htmlFor={`${prefix}_address1`}>Adres *</Label>
208
+ <Input
209
+ id={`${prefix}_address1`}
210
+ placeholder="Ulica i numer"
211
+ value={values.address1}
212
+ onChange={(e) => onChange("address1", e.target.value)}
213
+ className={errors[`${prefix}_address1`] ? "border-destructive" : ""}
214
+ />
215
+ {errors[`${prefix}_address1`] && (
216
+ <p className="mt-1 text-sm text-destructive">{errors[`${prefix}_address1`]}</p>
217
+ )}
218
+ </div>
219
+
220
+ <div>
221
+ <Label htmlFor={`${prefix}_address2`}>Mieszkanie, piętro (opcjonalnie)</Label>
222
+ <Input
223
+ id={`${prefix}_address2`}
224
+ value={values.address2}
225
+ onChange={(e) => onChange("address2", e.target.value)}
226
+ />
227
+ </div>
228
+
229
+ <div className="grid gap-4 sm:grid-cols-2">
230
+ <div>
231
+ <Label htmlFor={`${prefix}_zip`}>Kod pocztowy *</Label>
232
+ <Input
233
+ id={`${prefix}_zip`}
234
+ placeholder="00-000"
235
+ value={values.zip}
236
+ onChange={(e) => onChange("zip", e.target.value)}
237
+ className={errors[`${prefix}_zip`] ? "border-destructive" : ""}
238
+ />
239
+ {errors[`${prefix}_zip`] && (
240
+ <p className="mt-1 text-sm text-destructive">{errors[`${prefix}_zip`]}</p>
241
+ )}
242
+ </div>
243
+ <div>
244
+ <Label htmlFor={`${prefix}_city`}>Miasto *</Label>
245
+ <Input
246
+ id={`${prefix}_city`}
247
+ value={values.city}
248
+ onChange={(e) => onChange("city", e.target.value)}
249
+ className={errors[`${prefix}_city`] ? "border-destructive" : ""}
250
+ />
251
+ {errors[`${prefix}_city`] && (
252
+ <p className="mt-1 text-sm text-destructive">{errors[`${prefix}_city`]}</p>
253
+ )}
254
+ </div>
255
+ </div>
256
+
257
+ <div className="grid gap-4 sm:grid-cols-2">
258
+ <div>
259
+ <Label htmlFor={`${prefix}_country`}>Kraj *</Label>
260
+ <Select value={values.country} onValueChange={(value) => onChange("country", value)}>
261
+ <SelectTrigger className={errors[`${prefix}_country`] ? "border-destructive" : ""}>
262
+ <SelectValue placeholder="Wybierz kraj" />
263
+ </SelectTrigger>
264
+ <SelectContent>
265
+ {COUNTRIES.map((country) => (
266
+ <SelectItem key={country.code} value={country.code}>
267
+ {country.name}
268
+ </SelectItem>
269
+ ))}
270
+ </SelectContent>
271
+ </Select>
272
+ {errors[`${prefix}_country`] && (
273
+ <p className="mt-1 text-sm text-destructive">{errors[`${prefix}_country`]}</p>
274
+ )}
275
+ </div>
276
+ <div>
277
+ <Label htmlFor={`${prefix}_province`}>Województwo (opcjonalnie)</Label>
278
+ <Input
279
+ id={`${prefix}_province`}
280
+ value={values.province}
281
+ onChange={(e) => onChange("province", e.target.value)}
282
+ />
283
+ </div>
284
+ </div>
285
+ </div>
286
+ );
287
+ }
288
+
289
+ // ============================================================================
290
+ // CHECKOUT PAGE
291
+ // ============================================================================
292
+
293
+ export default function CheckoutPage() {
294
+ const router = useRouter();
295
+ const { cartId, clearCart } = useCartStore();
296
+ const { items, isLoading: isCartLoading } = useCartSync();
297
+
298
+ // Step management
299
+ const [currentStep, setCurrentStep] = useState<CheckoutStep>("contact");
300
+ const [checkoutId, setCheckoutId] = useState<string | null>(null);
301
+
302
+ // Detect if all items are gift cards (skip shipping)
303
+ const allGiftCards = items.length > 0 && items.every((item) => item.productType === "GIFT_CARD");
304
+ const hasGiftCards = items.some((item) => item.productType === "GIFT_CARD");
305
+
306
+ // Gift card recipient state
307
+ const [giftCardRecipients, setGiftCardRecipients] = useState<
308
+ Record<string, { recipientEmail?: string; recipientName?: string; message?: string }>
309
+ >({});
310
+
311
+ // Form state
312
+ const [formState, setFormState] = useState<CheckoutFormState>({
313
+ email: "",
314
+ phone: "",
315
+ shippingAddress: { ...emptyAddress },
316
+ billingAddress: { ...emptyAddress },
317
+ sameAsBilling: true,
318
+ selectedShippingRate: null,
319
+ selectedPaymentMethodId: null,
320
+ discountCode: "",
321
+ appliedDiscountCode: null,
322
+ giftCardCode: "",
323
+ acceptTerms: false,
324
+ acceptMarketing: false,
325
+ });
326
+
327
+ // Validation errors
328
+ const [errors, setErrors] = useState<Record<string, string>>({});
329
+
330
+ // Mutations
331
+ const createCheckout = useCheckoutCreate();
332
+ const updateEmail = useCheckoutEmailUpdate();
333
+ const updateShippingAddress = useCheckoutShippingAddressUpdate();
334
+ const updateBillingAddress = useCheckoutBillingAddressUpdate();
335
+ const updateShippingLine = useCheckoutShippingLineUpdate();
336
+ const applyDiscount = useCheckoutDiscountCodeApply();
337
+ const removeDiscount = useCheckoutDiscountCodeRemove();
338
+ const applyGiftCard = useCheckoutGiftCardApply();
339
+ const removeGiftCard = useCheckoutGiftCardRemove();
340
+ const completeCheckout = useCheckoutComplete();
341
+
342
+ // Query checkout for shipping rates
343
+ const { data: checkoutData, refetch: refetchCheckout } = useCheckout(checkoutId);
344
+ const checkout = checkoutData?.checkout;
345
+
346
+ // Sync discount codes from checkout (transferred from cart) to form state
347
+ useEffect(() => {
348
+ if (checkout?.discountCodes?.length > 0 && !formState.appliedDiscountCode) {
349
+ const applicableCode = checkout.discountCodes.find((dc: any) => dc.applicable);
350
+ if (applicableCode) {
351
+ setFormState((prev) => ({
352
+ ...prev,
353
+ appliedDiscountCode: applicableCode.code,
354
+ }));
355
+ }
356
+ }
357
+ }, [checkout?.discountCodes]);
358
+
359
+ // Loading state
360
+ const isLoading =
361
+ createCheckout.isPending ||
362
+ updateEmail.isPending ||
363
+ updateShippingAddress.isPending ||
364
+ updateBillingAddress.isPending ||
365
+ updateShippingLine.isPending ||
366
+ applyDiscount.isPending ||
367
+ removeDiscount.isPending ||
368
+ applyGiftCard.isPending ||
369
+ removeGiftCard.isPending ||
370
+ completeCheckout.isPending;
371
+
372
+ // Calculate totals from checkout or cart
373
+ const currencyCode = checkout?.currencyCode || items[0]?.price.currencyCode || "PLN";
374
+
375
+ // Use centralized formatPrice with proper locale based on currency
376
+ const formatPrice = (amount: string | number) => formatAmount(amount, currencyCode);
377
+
378
+ // ============================================================================
379
+ // STEP 1: Create checkout and update contact info
380
+ // ============================================================================
381
+
382
+ const handleContactSubmit = async () => {
383
+ // Validate contact info
384
+ const result = contactSchema.safeParse({
385
+ email: formState.email,
386
+ phone: formState.phone,
387
+ });
388
+ if (!result.success) {
389
+ const fieldErrors: Record<string, string> = {};
390
+ result.error.errors.forEach((err) => {
391
+ if (err.path[0]) {
392
+ fieldErrors[err.path[0] as string] = err.message;
393
+ }
394
+ });
395
+ setErrors(fieldErrors);
396
+ return;
397
+ }
398
+ setErrors({});
399
+
400
+ try {
401
+ // Create checkout if not exists
402
+ if (!checkoutId) {
403
+ const createResult = await createCheckout.mutateAsync({
404
+ input: {
405
+ cartId,
406
+ email: formState.email,
407
+ },
408
+ });
409
+
410
+ if (createResult.checkoutCreate.userErrors?.length > 0) {
411
+ toast.error(createResult.checkoutCreate.userErrors[0].message);
412
+ return;
413
+ }
414
+
415
+ if (createResult.checkoutCreate.checkout) {
416
+ setCheckoutId(createResult.checkoutCreate.checkout.id);
417
+ }
418
+ } else {
419
+ // Update email on existing checkout
420
+ const emailResult = await updateEmail.mutateAsync({
421
+ checkoutId,
422
+ email: formState.email,
423
+ });
424
+
425
+ if (emailResult.checkoutEmailUpdate.userErrors?.length > 0) {
426
+ toast.error(emailResult.checkoutEmailUpdate.userErrors[0].message);
427
+ return;
428
+ }
429
+ }
430
+
431
+ setCurrentStep(allGiftCards ? "payment" : "shipping");
432
+ } catch (error: any) {
433
+ toast.error(error.message || "Nie udało się przetworzyć danych kontaktowych");
434
+ }
435
+ };
436
+
437
+ // ============================================================================
438
+ // STEP 2: Update shipping address
439
+ // ============================================================================
440
+
441
+ const handleShippingSubmit = async () => {
442
+ // Validate address
443
+ const result = addressSchema.safeParse(formState.shippingAddress);
444
+ if (!result.success) {
445
+ const fieldErrors: Record<string, string> = {};
446
+ result.error.errors.forEach((err) => {
447
+ if (err.path[0]) {
448
+ fieldErrors[`shipping_${err.path[0]}`] = err.message;
449
+ }
450
+ });
451
+ setErrors(fieldErrors);
452
+ return;
453
+ }
454
+
455
+ // Validate billing address if different
456
+ if (!formState.sameAsBilling) {
457
+ const billingResult = addressSchema.safeParse(formState.billingAddress);
458
+ if (!billingResult.success) {
459
+ const fieldErrors: Record<string, string> = {};
460
+ billingResult.error.errors.forEach((err) => {
461
+ if (err.path[0]) {
462
+ fieldErrors[`billing_${err.path[0]}`] = err.message;
463
+ }
464
+ });
465
+ setErrors(fieldErrors);
466
+ return;
467
+ }
468
+ }
469
+
470
+ setErrors({});
471
+
472
+ if (!checkoutId) {
473
+ toast.error("Checkout nie znaleziony. Wróć i spróbuj ponownie.");
474
+ return;
475
+ }
476
+
477
+ try {
478
+ // Update shipping address
479
+ const addressResult = await updateShippingAddress.mutateAsync({
480
+ checkoutId,
481
+ shippingAddress: {
482
+ ...formState.shippingAddress,
483
+ phone: formState.phone,
484
+ },
485
+ });
486
+
487
+ if (addressResult.checkoutShippingAddressUpdate.userErrors?.length > 0) {
488
+ toast.error(addressResult.checkoutShippingAddressUpdate.userErrors[0].message);
489
+ return;
490
+ }
491
+
492
+ // Update billing address
493
+ const billingToUse = formState.sameAsBilling
494
+ ? formState.shippingAddress
495
+ : formState.billingAddress;
496
+
497
+ const billingResult = await updateBillingAddress.mutateAsync({
498
+ checkoutId,
499
+ billingAddress: {
500
+ ...billingToUse,
501
+ phone: formState.phone,
502
+ },
503
+ });
504
+
505
+ if (billingResult.checkoutBillingAddressUpdate.userErrors?.length > 0) {
506
+ toast.error(billingResult.checkoutBillingAddressUpdate.userErrors[0].message);
507
+ return;
508
+ }
509
+
510
+ // Refetch checkout to get shipping rates
511
+ await refetchCheckout();
512
+ setCurrentStep("delivery");
513
+ } catch (error: any) {
514
+ toast.error(error.message || "Nie udało się zaktualizować adresu");
515
+ }
516
+ };
517
+
518
+ // ============================================================================
519
+ // STEP 3: Select shipping method
520
+ // ============================================================================
521
+
522
+ const handleDeliverySubmit = async () => {
523
+ if (!formState.selectedShippingRate) {
524
+ setErrors({ shipping: "Wybierz metodę dostawy" });
525
+ return;
526
+ }
527
+ setErrors({});
528
+
529
+ if (!checkoutId) {
530
+ toast.error("Checkout nie znaleziony. Wróć i spróbuj ponownie.");
531
+ return;
532
+ }
533
+
534
+ try {
535
+ const shippingResult = await updateShippingLine.mutateAsync({
536
+ checkoutId,
537
+ shippingRateHandle: formState.selectedShippingRate,
538
+ });
539
+
540
+ if (shippingResult.checkoutShippingLineUpdate.userErrors?.length > 0) {
541
+ toast.error(shippingResult.checkoutShippingLineUpdate.userErrors[0].message);
542
+ return;
543
+ }
544
+
545
+ await refetchCheckout();
546
+ setCurrentStep("payment");
547
+ } catch (error: any) {
548
+ toast.error(error.message || "Nie udało się wybrać metody dostawy");
549
+ }
550
+ };
551
+
552
+ // ============================================================================
553
+ // STEP 4: Select payment method
554
+ // ============================================================================
555
+
556
+ const handlePaymentMethodSelect = (paymentMethodId: string) => {
557
+ // Validate payment method supports current currency
558
+ const selectedMethod = checkout?.availablePaymentMethods?.find(
559
+ (pm: any) => pm.id === paymentMethodId
560
+ );
561
+
562
+ if (selectedMethod?.supportedCurrencies?.length > 0) {
563
+ const supportsCurrency = selectedMethod.supportedCurrencies.includes(currencyCode);
564
+ if (!supportsCurrency) {
565
+ setErrors({
566
+ payment: `Ta metoda płatności nie obsługuje waluty ${currencyCode}. Wybierz inną metodę.`,
567
+ });
568
+ return;
569
+ }
570
+ }
571
+
572
+ setErrors({});
573
+ setFormState((prev) => ({ ...prev, selectedPaymentMethodId: paymentMethodId }));
574
+ };
575
+
576
+ const handlePaymentSubmit = async () => {
577
+ if (!formState.selectedPaymentMethodId) {
578
+ setErrors({ payment: "Wybierz metodę płatności" });
579
+ return;
580
+ }
581
+
582
+ // Validate selected payment method still exists and is valid
583
+ const selectedMethod = checkout?.availablePaymentMethods?.find(
584
+ (pm: any) => pm.id === formState.selectedPaymentMethodId
585
+ );
586
+
587
+ if (!selectedMethod) {
588
+ setErrors({ payment: "Wybrana metoda płatności jest niedostępna. Wybierz inną metodę." });
589
+ setFormState((prev) => ({ ...prev, selectedPaymentMethodId: null }));
590
+ return;
591
+ }
592
+
593
+ // Validate currency support
594
+ if (selectedMethod.supportedCurrencies?.length > 0) {
595
+ const supportsCurrency = selectedMethod.supportedCurrencies.includes(currencyCode);
596
+ if (!supportsCurrency) {
597
+ setErrors({
598
+ payment: `Metoda "${selectedMethod.name}" nie obsługuje waluty ${currencyCode}. Wybierz inną metodę.`,
599
+ });
600
+ return;
601
+ }
602
+ }
603
+
604
+ setErrors({});
605
+ setCurrentStep("review");
606
+ };
607
+
608
+ // ============================================================================
609
+ // DISCOUNT CODE
610
+ // ============================================================================
611
+
612
+ const handleApplyDiscount = async () => {
613
+ if (!formState.discountCode.trim()) {
614
+ return;
615
+ }
616
+
617
+ if (!checkoutId) {
618
+ toast.error("Checkout nie znaleziony");
619
+ return;
620
+ }
621
+
622
+ try {
623
+ const result = await applyDiscount.mutateAsync({
624
+ checkoutId,
625
+ discountCode: formState.discountCode.trim(),
626
+ });
627
+
628
+ const payload = result.checkoutDiscountCodeApply;
629
+
630
+ if (payload?.userErrors?.length > 0) {
631
+ toast.error(payload.userErrors[0].message);
632
+ return;
633
+ }
634
+
635
+ if (!payload?.checkout) {
636
+ toast.error("Nie udało się zastosować kodu rabatowego");
637
+ return;
638
+ }
639
+
640
+ setFormState((prev) => ({
641
+ ...prev,
642
+ appliedDiscountCode: prev.discountCode.trim(),
643
+ discountCode: "",
644
+ }));
645
+ await refetchCheckout();
646
+ toast.success("Kod rabatowy został zastosowany");
647
+ } catch (error: any) {
648
+ toast.error(error.message || "Nie udało się zastosować kodu rabatowego");
649
+ }
650
+ };
651
+
652
+ const handleRemoveDiscount = async () => {
653
+ if (!checkoutId || !formState.appliedDiscountCode) {
654
+ return;
655
+ }
656
+
657
+ try {
658
+ const result = await removeDiscount.mutateAsync({
659
+ checkoutId,
660
+ discountCode: formState.appliedDiscountCode,
661
+ });
662
+
663
+ if (result.checkoutDiscountCodeRemove.userErrors?.length > 0) {
664
+ toast.error(result.checkoutDiscountCodeRemove.userErrors[0].message);
665
+ return;
666
+ }
667
+
668
+ setFormState((prev) => ({
669
+ ...prev,
670
+ appliedDiscountCode: null,
671
+ }));
672
+ await refetchCheckout();
673
+ toast.success("Kod rabatowy został usunięty");
674
+ } catch (error: any) {
675
+ toast.error(error.message || "Nie udało się usunąć kodu rabatowego");
676
+ }
677
+ };
678
+
679
+ // ============================================================================
680
+ // GIFT CARD
681
+ // ============================================================================
682
+
683
+ const handleApplyGiftCard = async () => {
684
+ if (!formState.giftCardCode.trim()) {
685
+ return;
686
+ }
687
+
688
+ if (!checkoutId) {
689
+ toast.error("Checkout nie znaleziony");
690
+ return;
691
+ }
692
+
693
+ try {
694
+ const result = await applyGiftCard.mutateAsync({
695
+ checkoutId,
696
+ giftCardCode: formState.giftCardCode.trim().toUpperCase(),
697
+ });
698
+
699
+ if (result.checkoutGiftCardApply.userErrors?.length > 0) {
700
+ const error = result.checkoutGiftCardApply.userErrors[0];
701
+ // Translate common error codes to user-friendly messages
702
+ const errorMessages: Record<string, string> = {
703
+ GIFT_CARD_NOT_FOUND: "Nie znaleziono karty podarunkowej o podanym kodzie",
704
+ GIFT_CARD_EXPIRED: "Ta karta podarunkowa wygasła",
705
+ GIFT_CARD_DISABLED: "Ta karta podarunkowa jest wyłączona",
706
+ GIFT_CARD_NO_BALANCE: "Ta karta podarunkowa nie ma dostępnego salda",
707
+ GIFT_CARD_ALREADY_APPLIED: "Ta karta jest już zastosowana",
708
+ };
709
+ toast.error(errorMessages[error.code] || error.message);
710
+ return;
711
+ }
712
+
713
+ setFormState((prev) => ({
714
+ ...prev,
715
+ giftCardCode: "",
716
+ }));
717
+ await refetchCheckout();
718
+ toast.success("Karta podarunkowa została zastosowana");
719
+ } catch (error: any) {
720
+ toast.error(error.message || "Nie udało się zastosować karty podarunkowej");
721
+ }
722
+ };
723
+
724
+ const handleRemoveGiftCard = async (giftCardCode: string) => {
725
+ if (!checkoutId) {
726
+ return;
727
+ }
728
+
729
+ try {
730
+ const result = await removeGiftCard.mutateAsync({
731
+ checkoutId,
732
+ giftCardCode,
733
+ });
734
+
735
+ if (result.checkoutGiftCardRemove.userErrors?.length > 0) {
736
+ toast.error(result.checkoutGiftCardRemove.userErrors[0].message);
737
+ return;
738
+ }
739
+
740
+ await refetchCheckout();
741
+ toast.success("Karta podarunkowa została usunięta");
742
+ } catch (error: any) {
743
+ toast.error(error.message || "Nie udało się usunąć karty podarunkowej");
744
+ }
745
+ };
746
+
747
+ // ============================================================================
748
+ // STEP 5: Complete checkout
749
+ // ============================================================================
750
+
751
+ const handleCompleteCheckout = async () => {
752
+ if (!formState.acceptTerms) {
753
+ setErrors({ terms: "Musisz zaakceptować regulamin" });
754
+ return;
755
+ }
756
+
757
+ if (!formState.selectedPaymentMethodId) {
758
+ setErrors({ payment: "Wybierz metodę płatności" });
759
+ setCurrentStep("payment");
760
+ return;
761
+ }
762
+
763
+ setErrors({});
764
+
765
+ if (!checkoutId) {
766
+ toast.error("Checkout nie znaleziony. Wróć i spróbuj ponownie.");
767
+ return;
768
+ }
769
+
770
+ try {
771
+ const completeResult = await completeCheckout.mutateAsync({
772
+ checkoutId,
773
+ input: {
774
+ paymentMethodId: formState.selectedPaymentMethodId,
775
+ },
776
+ });
777
+
778
+ if (completeResult.checkoutComplete.userErrors?.length > 0) {
779
+ const error = completeResult.checkoutComplete.userErrors[0];
780
+ const errorCode = error.code;
781
+ const errorMessage = error.message;
782
+
783
+ // Handle specific payment-related error codes
784
+ switch (errorCode) {
785
+ case "INVALID_PAYMENT_METHOD":
786
+ case "PAYMENT_METHOD_NOT_FOUND":
787
+ setErrors({ payment: "Wybrana metoda płatności jest niedostępna. Wybierz inną metodę." });
788
+ setFormState((prev) => ({ ...prev, selectedPaymentMethodId: null }));
789
+ setCurrentStep("payment");
790
+ toast.error("Metoda płatności jest niedostępna");
791
+ break;
792
+ case "PAYMENT_METHOD_CURRENCY_NOT_SUPPORTED":
793
+ setErrors({ payment: `Metoda płatności nie obsługuje wybranej waluty (${currencyCode}).` });
794
+ setCurrentStep("payment");
795
+ toast.error("Waluta nie jest obsługiwana przez tę metodę płatności");
796
+ break;
797
+ case "PAYMENT_DECLINED":
798
+ toast.error("Płatność została odrzucona. Spróbuj ponownie lub wybierz inną metodę.");
799
+ setCurrentStep("payment");
800
+ break;
801
+ case "CHECKOUT_EXPIRED":
802
+ toast.error("Sesja checkout wygasła. Proszę odświeżyć stronę i spróbować ponownie.");
803
+ break;
804
+ case "INVENTORY_NOT_AVAILABLE":
805
+ toast.error("Niektóre produkty są niedostępne. Sprawdź swój koszyk.");
806
+ break;
807
+ default:
808
+ toast.error(errorMessage || "Nie udało się sfinalizować zamówienia");
809
+ }
810
+ return;
811
+ }
812
+
813
+ // Handle payment redirect or order completion
814
+ if (completeResult.checkoutComplete.paymentUrl) {
815
+ // Redirect to external payment provider (PayU, Stripe, P24)
816
+ clearCart();
817
+ window.location.href = completeResult.checkoutComplete.paymentUrl;
818
+ } else if (completeResult.checkoutComplete.order) {
819
+ // Order completed directly (COD, free order, bank transfer)
820
+ clearCart();
821
+ const orderId = completeResult.checkoutComplete.order.id;
822
+ const paymentType = formState.selectedPaymentMethodId
823
+ ? checkout?.availablePaymentMethods?.find(
824
+ (pm: any) => pm.id === formState.selectedPaymentMethodId
825
+ )?.type
826
+ : null;
827
+
828
+ // Pass payment type to success page for conditional display
829
+ const searchParams = paymentType === 'BANK_TRANSFER' ? '?payment=bank_transfer' : '';
830
+ router.push(`/checkout/success/${orderId}${searchParams}`);
831
+ } else {
832
+ // Fallback: no payment URL and no order - show error
833
+ toast.error("Wystąpił nieoczekiwany błąd. Skontaktuj się z obsługą.");
834
+ }
835
+ } catch (error: any) {
836
+ toast.error(error.message || "Nie udało się sfinalizować zamówienia");
837
+ }
838
+ };
839
+
840
+ // ============================================================================
841
+ // NAVIGATION
842
+ // ============================================================================
843
+
844
+ const goBack = () => {
845
+ const stepOrder: CheckoutStep[] = allGiftCards
846
+ ? ["contact", "payment", "review"]
847
+ : ["contact", "shipping", "delivery", "payment", "review"];
848
+ const currentIndex = stepOrder.indexOf(currentStep);
849
+ if (currentIndex > 0) {
850
+ setCurrentStep(stepOrder[currentIndex - 1]);
851
+ }
852
+ };
853
+
854
+ // ============================================================================
855
+ // RENDER HELPERS
856
+ // ============================================================================
857
+
858
+ const availableShippingRates: ShippingRate[] = checkout?.availableShippingRates || [];
859
+ const shippingRatesReady = checkout?.shippingRatesReady ?? false;
860
+
861
+ // Wait for cart data to load
862
+ if (isCartLoading) {
863
+ return (
864
+ <div className="container mx-auto px-4 py-16 text-center">
865
+ <Loader2 className="mx-auto h-8 w-8 animate-spin text-muted-foreground" />
866
+ <p className="mt-4 text-muted-foreground">Ładowanie...</p>
867
+ </div>
868
+ );
869
+ }
870
+
871
+ // Empty cart check
872
+ if (items.length === 0 && !checkoutId) {
873
+ return (
874
+ <div className="container mx-auto px-4 py-16 text-center">
875
+ <ShoppingBag className="mx-auto h-16 w-16 text-muted-foreground" />
876
+ <h1 className="mt-4 text-2xl font-bold">Twój koszyk jest pusty</h1>
877
+ <p className="mt-2 text-muted-foreground">
878
+ Dodaj produkty do koszyka, aby kontynuować zakupy.
879
+ </p>
880
+ <Button className="mt-6" asChild>
881
+ <Link href="/products">Przeglądaj produkty</Link>
882
+ </Button>
883
+ </div>
884
+ );
885
+ }
886
+
887
+ // ============================================================================
888
+ // STEP CONFIG
889
+ // ============================================================================
890
+
891
+ const allSteps: { id: CheckoutStep; label: string; icon: React.ReactNode }[] = [
892
+ { id: "contact", label: "Kontakt", icon: <User className="h-4 w-4" /> },
893
+ { id: "shipping", label: "Adres", icon: <Package className="h-4 w-4" /> },
894
+ { id: "delivery", label: "Dostawa", icon: <Truck className="h-4 w-4" /> },
895
+ { id: "payment", label: "Płatność", icon: <CreditCard className="h-4 w-4" /> },
896
+ { id: "review", label: "Podsumowanie", icon: <ClipboardCheck className="h-4 w-4" /> },
897
+ ];
898
+ // Skip shipping and delivery steps when all items are gift cards
899
+ const steps = allGiftCards
900
+ ? allSteps.filter((s) => s.id !== "shipping" && s.id !== "delivery")
901
+ : allSteps;
902
+
903
+ const currentStepIndex = steps.findIndex((s) => s.id === currentStep);
904
+
905
+ // ============================================================================
906
+ // RENDER
907
+ // ============================================================================
908
+
909
+ return (
910
+ <div className="container mx-auto px-4 py-8">
911
+ <Breadcrumbs className="mb-6" />
912
+
913
+ <div className="mb-8 flex items-center justify-between">
914
+ <h1 className="text-3xl font-bold">Checkout</h1>
915
+ <Link href="/auth/login" className="text-sm text-primary hover:underline">
916
+ Masz konto? Zaloguj się
917
+ </Link>
918
+ </div>
919
+
920
+ {/* Step Indicator */}
921
+ <div className="mb-8">
922
+ <div className="flex items-center justify-between">
923
+ {steps.map((step, index) => (
924
+ <div key={step.id} className="flex items-center">
925
+ <div
926
+ className={`flex h-10 w-10 items-center justify-center rounded-full text-sm font-medium transition-colors ${
927
+ index < currentStepIndex
928
+ ? "bg-primary text-primary-foreground"
929
+ : index === currentStepIndex
930
+ ? "bg-primary text-primary-foreground ring-4 ring-primary/20"
931
+ : "bg-muted text-muted-foreground"
932
+ }`}
933
+ >
934
+ {index < currentStepIndex ? <Check className="h-5 w-5" /> : step.icon}
935
+ </div>
936
+ <span
937
+ className={`ml-2 hidden text-sm font-medium sm:inline ${
938
+ index <= currentStepIndex ? "text-foreground" : "text-muted-foreground"
939
+ }`}
940
+ >
941
+ {step.label}
942
+ </span>
943
+ {index < steps.length - 1 && (
944
+ <div
945
+ className={`mx-2 h-0.5 w-8 sm:mx-4 sm:w-16 transition-colors ${
946
+ index < currentStepIndex ? "bg-primary" : "bg-muted"
947
+ }`}
948
+ />
949
+ )}
950
+ </div>
951
+ ))}
952
+ </div>
953
+ </div>
954
+
955
+ <div className="grid gap-8 lg:grid-cols-3">
956
+ {/* Main Content */}
957
+ <div className="lg:col-span-2 space-y-6">
958
+ {/* Step 1: Contact */}
959
+ {currentStep === "contact" && (
960
+ <Card>
961
+ <CardHeader>
962
+ <CardTitle className="flex items-center gap-2">
963
+ <User className="h-5 w-5" />
964
+ Dane kontaktowe
965
+ </CardTitle>
966
+ <CardDescription>
967
+ Podaj swój email i telefon - wyślemy na nie potwierdzenie zamówienia
968
+ </CardDescription>
969
+ </CardHeader>
970
+ <CardContent className="space-y-4">
971
+ <div>
972
+ <Label htmlFor="email">Email *</Label>
973
+ <Input
974
+ id="email"
975
+ type="email"
976
+ placeholder="twoj@email.pl"
977
+ value={formState.email}
978
+ onChange={(e) =>
979
+ setFormState((prev) => ({ ...prev, email: e.target.value }))
980
+ }
981
+ className={errors.email ? "border-destructive" : ""}
982
+ />
983
+ {errors.email && (
984
+ <p className="mt-1 text-sm text-destructive">{errors.email}</p>
985
+ )}
986
+ </div>
987
+
988
+ <div>
989
+ <Label htmlFor="phone">Telefon *</Label>
990
+ <Input
991
+ id="phone"
992
+ type="tel"
993
+ placeholder="+48 123 456 789"
994
+ value={formState.phone}
995
+ onChange={(e) =>
996
+ setFormState((prev) => ({ ...prev, phone: e.target.value }))
997
+ }
998
+ className={errors.phone ? "border-destructive" : ""}
999
+ />
1000
+ {errors.phone && (
1001
+ <p className="mt-1 text-sm text-destructive">{errors.phone}</p>
1002
+ )}
1003
+ <p className="mt-1 text-xs text-muted-foreground">
1004
+ Kurier może potrzebować kontaktu w sprawie dostawy
1005
+ </p>
1006
+ </div>
1007
+
1008
+ <div className="flex items-center space-x-2 pt-2">
1009
+ <Checkbox
1010
+ id="marketing"
1011
+ checked={formState.acceptMarketing}
1012
+ onCheckedChange={(checked) =>
1013
+ setFormState((prev) => ({ ...prev, acceptMarketing: checked === true }))
1014
+ }
1015
+ />
1016
+ <Label htmlFor="marketing" className="text-sm font-normal cursor-pointer">
1017
+ Chcę otrzymywać informacje o promocjach i nowościach
1018
+ </Label>
1019
+ </div>
1020
+
1021
+ {/* Gift card recipient fields */}
1022
+ {hasGiftCards && (
1023
+ <div className="space-y-4 border-t pt-4">
1024
+ <div className="flex items-center gap-2 text-sm font-medium">
1025
+ <Gift className="h-4 w-4" />
1026
+ Dane odbiorcy karty podarunkowej
1027
+ </div>
1028
+ <p className="text-xs text-muted-foreground">
1029
+ Opcjonalnie: podaj dane osoby, która otrzyma kartę podarunkową
1030
+ </p>
1031
+ {items
1032
+ .filter((item) => item.productType === "GIFT_CARD")
1033
+ .map((item) => (
1034
+ <div key={item.variantId} className="space-y-3 rounded-lg border p-4">
1035
+ <p className="text-sm font-medium">{item.productTitle} — {item.variantTitle}</p>
1036
+ <div>
1037
+ <Label htmlFor={`recipient-email-${item.variantId}`}>E-mail odbiorcy</Label>
1038
+ <Input
1039
+ id={`recipient-email-${item.variantId}`}
1040
+ type="email"
1041
+ placeholder="odbiorca@email.pl"
1042
+ value={giftCardRecipients[item.variantId]?.recipientEmail || ""}
1043
+ onChange={(e) =>
1044
+ setGiftCardRecipients((prev) => ({
1045
+ ...prev,
1046
+ [item.variantId]: {
1047
+ ...prev[item.variantId],
1048
+ recipientEmail: e.target.value,
1049
+ },
1050
+ }))
1051
+ }
1052
+ />
1053
+ </div>
1054
+ <div>
1055
+ <Label htmlFor={`recipient-name-${item.variantId}`}>Imię odbiorcy</Label>
1056
+ <Input
1057
+ id={`recipient-name-${item.variantId}`}
1058
+ type="text"
1059
+ placeholder="Jan"
1060
+ value={giftCardRecipients[item.variantId]?.recipientName || ""}
1061
+ onChange={(e) =>
1062
+ setGiftCardRecipients((prev) => ({
1063
+ ...prev,
1064
+ [item.variantId]: {
1065
+ ...prev[item.variantId],
1066
+ recipientName: e.target.value,
1067
+ },
1068
+ }))
1069
+ }
1070
+ />
1071
+ </div>
1072
+ <div>
1073
+ <Label htmlFor={`recipient-message-${item.variantId}`}>Wiadomość</Label>
1074
+ <Input
1075
+ id={`recipient-message-${item.variantId}`}
1076
+ type="text"
1077
+ placeholder="Wszystkiego najlepszego!"
1078
+ value={giftCardRecipients[item.variantId]?.message || ""}
1079
+ onChange={(e) =>
1080
+ setGiftCardRecipients((prev) => ({
1081
+ ...prev,
1082
+ [item.variantId]: {
1083
+ ...prev[item.variantId],
1084
+ message: e.target.value,
1085
+ },
1086
+ }))
1087
+ }
1088
+ />
1089
+ </div>
1090
+ </div>
1091
+ ))}
1092
+ </div>
1093
+ )}
1094
+
1095
+ <Button onClick={handleContactSubmit} disabled={isLoading} className="w-full" size="lg">
1096
+ {isLoading ? (
1097
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
1098
+ ) : (
1099
+ <ChevronRight className="mr-2 h-4 w-4" />
1100
+ )}
1101
+ {allGiftCards ? "Dalej: Płatność" : "Dalej: Adres dostawy"}
1102
+ </Button>
1103
+ </CardContent>
1104
+ </Card>
1105
+ )}
1106
+
1107
+ {/* Step 2: Shipping Address */}
1108
+ {currentStep === "shipping" && (
1109
+ <>
1110
+ <Card>
1111
+ <CardHeader>
1112
+ <CardTitle className="flex items-center gap-2">
1113
+ <Package className="h-5 w-5" />
1114
+ Adres dostawy
1115
+ </CardTitle>
1116
+ <CardDescription>Podaj adres, na który mamy wysłać zamówienie</CardDescription>
1117
+ </CardHeader>
1118
+ <CardContent>
1119
+ <AddressFormFields
1120
+ prefix="shipping"
1121
+ values={formState.shippingAddress}
1122
+ errors={errors}
1123
+ onChange={(field, value) =>
1124
+ setFormState((prev) => ({
1125
+ ...prev,
1126
+ shippingAddress: { ...prev.shippingAddress, [field]: value },
1127
+ }))
1128
+ }
1129
+ />
1130
+ </CardContent>
1131
+ </Card>
1132
+
1133
+ <Card>
1134
+ <CardHeader>
1135
+ <CardTitle className="flex items-center gap-2">
1136
+ <CreditCard className="h-5 w-5" />
1137
+ Adres rozliczeniowy
1138
+ </CardTitle>
1139
+ </CardHeader>
1140
+ <CardContent className="space-y-4">
1141
+ <div className="flex items-center space-x-2">
1142
+ <Checkbox
1143
+ id="sameAsBilling"
1144
+ checked={formState.sameAsBilling}
1145
+ onCheckedChange={(checked) =>
1146
+ setFormState((prev) => ({ ...prev, sameAsBilling: checked === true }))
1147
+ }
1148
+ />
1149
+ <Label htmlFor="sameAsBilling" className="cursor-pointer">
1150
+ Taki sam jak adres dostawy
1151
+ </Label>
1152
+ </div>
1153
+
1154
+ {!formState.sameAsBilling && (
1155
+ <AddressFormFields
1156
+ prefix="billing"
1157
+ values={formState.billingAddress}
1158
+ errors={errors}
1159
+ onChange={(field, value) =>
1160
+ setFormState((prev) => ({
1161
+ ...prev,
1162
+ billingAddress: { ...prev.billingAddress, [field]: value },
1163
+ }))
1164
+ }
1165
+ />
1166
+ )}
1167
+ </CardContent>
1168
+ </Card>
1169
+
1170
+ <div className="flex gap-4">
1171
+ <Button variant="outline" onClick={goBack} size="lg">
1172
+ <ChevronLeft className="mr-2 h-4 w-4" />
1173
+ Wstecz
1174
+ </Button>
1175
+ <Button onClick={handleShippingSubmit} disabled={isLoading} className="flex-1" size="lg">
1176
+ {isLoading ? (
1177
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
1178
+ ) : (
1179
+ <ChevronRight className="mr-2 h-4 w-4" />
1180
+ )}
1181
+ Dalej: Metoda dostawy
1182
+ </Button>
1183
+ </div>
1184
+ </>
1185
+ )}
1186
+
1187
+ {/* Step 3: Delivery Method */}
1188
+ {currentStep === "delivery" && (
1189
+ <Card>
1190
+ <CardHeader>
1191
+ <CardTitle className="flex items-center gap-2">
1192
+ <Truck className="h-5 w-5" />
1193
+ Metoda dostawy
1194
+ </CardTitle>
1195
+ <CardDescription>Wybierz sposób dostarczenia zamówienia</CardDescription>
1196
+ </CardHeader>
1197
+ <CardContent className="space-y-4">
1198
+ {!shippingRatesReady ? (
1199
+ <div className="flex items-center justify-center py-8">
1200
+ <Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
1201
+ <span className="ml-2 text-muted-foreground">Ładowanie metod dostawy...</span>
1202
+ </div>
1203
+ ) : availableShippingRates.length === 0 ? (
1204
+ <div className="rounded-lg border border-destructive/50 bg-destructive/10 p-4 text-center">
1205
+ <p className="text-destructive">
1206
+ Brak dostępnych metod dostawy dla podanego adresu.
1207
+ </p>
1208
+ <Button variant="outline" onClick={goBack} className="mt-4">
1209
+ Zmień adres dostawy
1210
+ </Button>
1211
+ </div>
1212
+ ) : (
1213
+ <RadioGroup
1214
+ value={formState.selectedShippingRate || ""}
1215
+ onValueChange={(value) =>
1216
+ setFormState((prev) => ({ ...prev, selectedShippingRate: value }))
1217
+ }
1218
+ >
1219
+ {availableShippingRates.map((rate) => (
1220
+ <div
1221
+ key={rate.handle}
1222
+ className={`flex items-center space-x-3 rounded-lg border p-4 transition-colors cursor-pointer hover:bg-muted/50 ${
1223
+ formState.selectedShippingRate === rate.handle
1224
+ ? "border-primary bg-primary/5"
1225
+ : ""
1226
+ }`}
1227
+ onClick={() =>
1228
+ setFormState((prev) => ({ ...prev, selectedShippingRate: rate.handle }))
1229
+ }
1230
+ >
1231
+ <RadioGroupItem value={rate.handle} id={rate.handle} />
1232
+ <Label
1233
+ htmlFor={rate.handle}
1234
+ className="flex flex-1 cursor-pointer items-center justify-between"
1235
+ >
1236
+ <div className="flex items-center gap-3">
1237
+ <Truck className="h-5 w-5 text-muted-foreground" />
1238
+ <span className="font-medium">{rate.title}</span>
1239
+ </div>
1240
+ <span className="font-semibold">{formatPrice(rate.price.amount)}</span>
1241
+ </Label>
1242
+ </div>
1243
+ ))}
1244
+ </RadioGroup>
1245
+ )}
1246
+ {errors.shipping && (
1247
+ <p className="text-sm text-destructive">{errors.shipping}</p>
1248
+ )}
1249
+ <div className="flex gap-4 pt-4">
1250
+ <Button variant="outline" onClick={goBack} size="lg">
1251
+ <ChevronLeft className="mr-2 h-4 w-4" />
1252
+ Wstecz
1253
+ </Button>
1254
+ <Button
1255
+ onClick={handleDeliverySubmit}
1256
+ disabled={isLoading || !formState.selectedShippingRate}
1257
+ className="flex-1"
1258
+ size="lg"
1259
+ >
1260
+ {isLoading ? (
1261
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
1262
+ ) : (
1263
+ <ChevronRight className="mr-2 h-4 w-4" />
1264
+ )}
1265
+ Dalej: Płatność
1266
+ </Button>
1267
+ </div>
1268
+ </CardContent>
1269
+ </Card>
1270
+ )}
1271
+
1272
+ {/* Step 4: Payment */}
1273
+ {currentStep === "payment" && checkout && (
1274
+ <Card>
1275
+ <CardHeader>
1276
+ <CardTitle className="flex items-center gap-2">
1277
+ <CreditCard className="h-5 w-5" />
1278
+ Metoda płatności
1279
+ </CardTitle>
1280
+ <CardDescription>Wybierz sposób zapłaty za zamówienie</CardDescription>
1281
+ </CardHeader>
1282
+ <CardContent className="space-y-4">
1283
+ {/* PaymentStep component handles payment method selection */}
1284
+ <PaymentStep
1285
+ availablePaymentMethods={(checkout.availablePaymentMethods || []).map(
1286
+ (pm: any): PaymentMethod => ({
1287
+ id: pm.id,
1288
+ name: pm.name,
1289
+ provider: pm.provider,
1290
+ type: pm.type,
1291
+ icon: pm.icon,
1292
+ description: pm.description,
1293
+ isDefault: pm.isDefault,
1294
+ supportedCurrencies: pm.supportedCurrencies,
1295
+ position: pm.position || 0,
1296
+ })
1297
+ )}
1298
+ selectedPaymentMethodId={formState.selectedPaymentMethodId}
1299
+ onPaymentMethodSelect={handlePaymentMethodSelect}
1300
+ isLoading={isLoading}
1301
+ error={errors.payment}
1302
+ checkoutReady={!!checkout}
1303
+ />
1304
+ {errors.payment && (
1305
+ <p className="text-sm text-destructive">{errors.payment}</p>
1306
+ )}
1307
+ <div className="flex gap-4 pt-4">
1308
+ <Button variant="outline" onClick={goBack} size="lg">
1309
+ <ChevronLeft className="mr-2 h-4 w-4" />
1310
+ Wstecz
1311
+ </Button>
1312
+ <Button
1313
+ onClick={handlePaymentSubmit}
1314
+ disabled={isLoading || !formState.selectedPaymentMethodId}
1315
+ className="flex-1"
1316
+ size="lg"
1317
+ >
1318
+ {isLoading ? (
1319
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
1320
+ ) : (
1321
+ <ChevronRight className="mr-2 h-4 w-4" />
1322
+ )}
1323
+ Dalej: Podsumowanie
1324
+ </Button>
1325
+ </div>
1326
+ </CardContent>
1327
+ </Card>
1328
+ )}
1329
+
1330
+ {/* Step 5: Review */}
1331
+ {currentStep === "review" && checkout && (
1332
+ <Card>
1333
+ <CardHeader>
1334
+ <CardTitle className="flex items-center gap-2">
1335
+ <ClipboardCheck className="h-5 w-5" />
1336
+ Podsumowanie zamówienia
1337
+ </CardTitle>
1338
+ <CardDescription>Sprawdź wszystkie dane przed finalizacją</CardDescription>
1339
+ </CardHeader>
1340
+ <CardContent className="space-y-6">
1341
+ {/* Contact */}
1342
+ <div className="flex items-start justify-between rounded-lg border p-4">
1343
+ <div>
1344
+ <h3 className="mb-1 font-medium">Kontakt</h3>
1345
+ <p className="text-sm text-muted-foreground">{checkout.email}</p>
1346
+ <p className="text-sm text-muted-foreground">{formState.phone}</p>
1347
+ </div>
1348
+ <Button
1349
+ variant="ghost"
1350
+ size="sm"
1351
+ onClick={() => setCurrentStep("contact")}
1352
+ >
1353
+ Zmień
1354
+ </Button>
1355
+ </div>
1356
+
1357
+ {/* Shipping Address (hidden for gift-card-only orders) */}
1358
+ {!allGiftCards && (
1359
+ <div className="flex items-start justify-between rounded-lg border p-4">
1360
+ <div>
1361
+ <h3 className="mb-1 font-medium">Adres dostawy</h3>
1362
+ {checkout.shippingAddress && (
1363
+ <p className="text-sm text-muted-foreground">
1364
+ {checkout.shippingAddress.firstName} {checkout.shippingAddress.lastName}
1365
+ <br />
1366
+ {checkout.shippingAddress.address1}
1367
+ {checkout.shippingAddress.address2 && (
1368
+ <>
1369
+ <br />
1370
+ {checkout.shippingAddress.address2}
1371
+ </>
1372
+ )}
1373
+ <br />
1374
+ {checkout.shippingAddress.zip} {checkout.shippingAddress.city}
1375
+ <br />
1376
+ {COUNTRIES.find((c) => c.code === checkout.shippingAddress?.country)?.name ||
1377
+ checkout.shippingAddress.country}
1378
+ </p>
1379
+ )}
1380
+ </div>
1381
+ <Button
1382
+ variant="ghost"
1383
+ size="sm"
1384
+ onClick={() => setCurrentStep("shipping")}
1385
+ >
1386
+ Zmień
1387
+ </Button>
1388
+ </div>
1389
+ )}
1390
+
1391
+ {/* Shipping Method (hidden for gift-card-only orders) */}
1392
+ {!allGiftCards && (
1393
+ <div className="flex items-start justify-between rounded-lg border p-4">
1394
+ <div>
1395
+ <h3 className="mb-1 font-medium">Metoda dostawy</h3>
1396
+ {checkout.shippingLine && (
1397
+ <p className="text-sm text-muted-foreground">
1398
+ {checkout.shippingLine.title} -{" "}
1399
+ {formatPrice(checkout.shippingLine.price.amount)}
1400
+ </p>
1401
+ )}
1402
+ </div>
1403
+ <Button
1404
+ variant="ghost"
1405
+ size="sm"
1406
+ onClick={() => setCurrentStep("delivery")}
1407
+ >
1408
+ Zmień
1409
+ </Button>
1410
+ </div>
1411
+ )}
1412
+
1413
+ {/* Payment Method */}
1414
+ <div className="flex items-start justify-between rounded-lg border p-4">
1415
+ <div>
1416
+ <h3 className="mb-1 font-medium">Metoda płatności</h3>
1417
+ {formState.selectedPaymentMethodId && (
1418
+ <p className="text-sm text-muted-foreground">
1419
+ {checkout.availablePaymentMethods?.find(
1420
+ (pm: any) => pm.id === formState.selectedPaymentMethodId
1421
+ )?.name || "Wybrana metoda płatności"}
1422
+ </p>
1423
+ )}
1424
+ </div>
1425
+ <Button
1426
+ variant="ghost"
1427
+ size="sm"
1428
+ onClick={() => setCurrentStep("payment")}
1429
+ >
1430
+ Zmień
1431
+ </Button>
1432
+ </div>
1433
+
1434
+ {/* Terms acceptance */}
1435
+ <div className="space-y-4 rounded-lg border p-4">
1436
+ <div className="flex items-start space-x-2">
1437
+ <Checkbox
1438
+ id="terms"
1439
+ checked={formState.acceptTerms}
1440
+ onCheckedChange={(checked) =>
1441
+ setFormState((prev) => ({ ...prev, acceptTerms: checked === true }))
1442
+ }
1443
+ className={errors.terms ? "border-destructive" : ""}
1444
+ />
1445
+ <div className="grid gap-1.5 leading-none">
1446
+ <Label htmlFor="terms" className="cursor-pointer">
1447
+ Akceptuję{" "}
1448
+ <Link href="/terms" className="text-primary hover:underline">
1449
+ regulamin sklepu
1450
+ </Link>{" "}
1451
+ oraz{" "}
1452
+ <Link href="/privacy" className="text-primary hover:underline">
1453
+ politykę prywatności
1454
+ </Link>{" "}
1455
+ *
1456
+ </Label>
1457
+ {errors.terms && (
1458
+ <p className="text-sm text-destructive">{errors.terms}</p>
1459
+ )}
1460
+ </div>
1461
+ </div>
1462
+ </div>
1463
+
1464
+ <div className="flex gap-4">
1465
+ <Button variant="outline" onClick={goBack} size="lg">
1466
+ <ChevronLeft className="mr-2 h-4 w-4" />
1467
+ Wstecz
1468
+ </Button>
1469
+ <Button
1470
+ onClick={handleCompleteCheckout}
1471
+ disabled={isLoading || !formState.acceptTerms}
1472
+ className="flex-1"
1473
+ size="lg"
1474
+ >
1475
+ {isLoading ? (
1476
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
1477
+ ) : (
1478
+ <CreditCard className="mr-2 h-4 w-4" />
1479
+ )}
1480
+ Zapłać i zamów
1481
+ </Button>
1482
+ </div>
1483
+ </CardContent>
1484
+ </Card>
1485
+ )}
1486
+ </div>
1487
+
1488
+ {/* Order Summary Sidebar */}
1489
+ <div className="lg:col-span-1">
1490
+ <Card className="sticky top-4">
1491
+ <CardHeader>
1492
+ <CardTitle className="flex items-center gap-2">
1493
+ <ShoppingBag className="h-5 w-5" />
1494
+ Twoje zamówienie
1495
+ </CardTitle>
1496
+ </CardHeader>
1497
+ <CardContent className="space-y-4">
1498
+ {/* Line Items */}
1499
+ <div className="space-y-3">
1500
+ {(checkout?.lineItems || items).map((item: any) => (
1501
+ <div
1502
+ key={item.id || item.lineId || item.variantId}
1503
+ className="flex gap-3 text-sm"
1504
+ >
1505
+ <div className="relative h-16 w-16 flex-shrink-0 overflow-hidden rounded-md border bg-muted">
1506
+ {item.image?.url ? (
1507
+ <img
1508
+ src={item.image.url}
1509
+ alt={item.title || item.productTitle}
1510
+ className="h-full w-full object-cover"
1511
+ />
1512
+ ) : (
1513
+ <div className="flex h-full w-full items-center justify-center">
1514
+ <Package className="h-6 w-6 text-muted-foreground" />
1515
+ </div>
1516
+ )}
1517
+ <span className="absolute -right-2 -top-2 flex h-5 w-5 items-center justify-center rounded-full bg-primary text-xs text-primary-foreground">
1518
+ {item.quantity}
1519
+ </span>
1520
+ </div>
1521
+ <div className="flex-1">
1522
+ <p className="font-medium line-clamp-2">
1523
+ {item.title || item.productTitle}
1524
+ </p>
1525
+ {item.variantTitle && item.variantTitle !== "Default" && (
1526
+ <p className="text-xs text-muted-foreground">{item.variantTitle}</p>
1527
+ )}
1528
+ </div>
1529
+ <p className="font-medium">
1530
+ {formatPrice(
1531
+ item.totalPrice?.amount ||
1532
+ parseFloat(item.price.amount) * item.quantity
1533
+ )}
1534
+ </p>
1535
+ </div>
1536
+ ))}
1537
+ </div>
1538
+
1539
+ {/* Discount Code */}
1540
+ {checkoutId && (
1541
+ <div className="border-t pt-4">
1542
+ {formState.appliedDiscountCode ? (
1543
+ <div className="flex items-center justify-between rounded-lg bg-green-50 p-3 dark:bg-green-950">
1544
+ <div className="flex items-center gap-2">
1545
+ <Tag className="h-4 w-4 text-green-600" />
1546
+ <span className="text-sm font-medium text-green-600">
1547
+ {formState.appliedDiscountCode}
1548
+ </span>
1549
+ </div>
1550
+ <Button
1551
+ variant="ghost"
1552
+ size="sm"
1553
+ onClick={handleRemoveDiscount}
1554
+ disabled={isLoading}
1555
+ className="h-8 w-8 p-0"
1556
+ >
1557
+ <X className="h-4 w-4" />
1558
+ </Button>
1559
+ </div>
1560
+ ) : (
1561
+ <div className="flex gap-2">
1562
+ <Input
1563
+ placeholder="Kod rabatowy"
1564
+ value={formState.discountCode}
1565
+ onChange={(e) =>
1566
+ setFormState((prev) => ({ ...prev, discountCode: e.target.value }))
1567
+ }
1568
+ className="flex-1"
1569
+ />
1570
+ <Button
1571
+ variant="outline"
1572
+ onClick={handleApplyDiscount}
1573
+ disabled={isLoading || !formState.discountCode.trim()}
1574
+ >
1575
+ {applyDiscount.isPending ? (
1576
+ <Loader2 className="h-4 w-4 animate-spin" />
1577
+ ) : (
1578
+ "Zastosuj"
1579
+ )}
1580
+ </Button>
1581
+ </div>
1582
+ )}
1583
+ </div>
1584
+ )}
1585
+
1586
+ {/* Gift Card */}
1587
+ {checkoutId && (
1588
+ <div className="border-t pt-4 space-y-3">
1589
+ <p className="text-sm font-medium">Karta podarunkowa</p>
1590
+
1591
+ {/* Applied Gift Cards */}
1592
+ {checkout?.appliedGiftCards && checkout.appliedGiftCards.length > 0 && (
1593
+ <div className="space-y-2">
1594
+ {checkout.appliedGiftCards.map((giftCard: any) => (
1595
+ <div
1596
+ key={giftCard.lastCharacters}
1597
+ className="flex items-center justify-between rounded-lg bg-purple-50 p-3 dark:bg-purple-950"
1598
+ >
1599
+ <div className="flex items-center gap-2">
1600
+ <Gift className="h-4 w-4 text-purple-600" />
1601
+ <div className="text-sm">
1602
+ <span className="font-medium text-purple-600">
1603
+ {giftCard.maskedCode}
1604
+ </span>
1605
+ <span className="text-purple-600/70 ml-2">
1606
+ -{formatPrice(giftCard.appliedAmount?.amount || "0")}
1607
+ </span>
1608
+ </div>
1609
+ </div>
1610
+ <Button
1611
+ variant="ghost"
1612
+ size="sm"
1613
+ onClick={() => handleRemoveGiftCard(giftCard.maskedCode)}
1614
+ disabled={isLoading}
1615
+ className="h-8 w-8 p-0"
1616
+ >
1617
+ <X className="h-4 w-4" />
1618
+ </Button>
1619
+ </div>
1620
+ ))}
1621
+ </div>
1622
+ )}
1623
+
1624
+ {/* Gift Card Input */}
1625
+ <div className="flex gap-2">
1626
+ <Input
1627
+ placeholder="Kod karty podarunkowej"
1628
+ value={formState.giftCardCode}
1629
+ onChange={(e) =>
1630
+ setFormState((prev) => ({ ...prev, giftCardCode: e.target.value.toUpperCase() }))
1631
+ }
1632
+ onKeyDown={(e) => {
1633
+ if (e.key === "Enter") {
1634
+ e.preventDefault();
1635
+ handleApplyGiftCard();
1636
+ }
1637
+ }}
1638
+ className="flex-1 uppercase"
1639
+ />
1640
+ <Button
1641
+ variant="outline"
1642
+ onClick={handleApplyGiftCard}
1643
+ disabled={isLoading || !formState.giftCardCode.trim()}
1644
+ >
1645
+ {applyGiftCard.isPending ? (
1646
+ <Loader2 className="h-4 w-4 animate-spin" />
1647
+ ) : (
1648
+ "Zastosuj"
1649
+ )}
1650
+ </Button>
1651
+ </div>
1652
+ </div>
1653
+ )}
1654
+
1655
+ {/* Totals */}
1656
+ <div className="border-t pt-4 space-y-2">
1657
+ {/* Subtotal */}
1658
+ <div className="flex justify-between text-sm">
1659
+ <span className="text-muted-foreground">Produkty</span>
1660
+ <span>
1661
+ {formatPrice(
1662
+ checkout?.subtotalPrice?.amount ||
1663
+ items.reduce(
1664
+ (sum, item) => sum + parseFloat(item.price.amount) * item.quantity,
1665
+ 0
1666
+ )
1667
+ )}
1668
+ </span>
1669
+ </div>
1670
+
1671
+ {/* Shipping */}
1672
+ {checkout?.shippingLine ? (
1673
+ <div className="flex justify-between text-sm">
1674
+ <span className="text-muted-foreground">Dostawa</span>
1675
+ <span>{formatPrice(checkout.totalShippingPrice?.amount || "0")}</span>
1676
+ </div>
1677
+ ) : (
1678
+ <div className="flex justify-between text-sm">
1679
+ <span className="text-muted-foreground">Dostawa</span>
1680
+ <span className="text-muted-foreground">Obliczona w następnym kroku</span>
1681
+ </div>
1682
+ )}
1683
+
1684
+ {/* Discount */}
1685
+ {checkout?.totalDiscounts && parseFloat(checkout.totalDiscounts.amount) > 0 && (
1686
+ <div className="flex justify-between text-sm text-green-600">
1687
+ <span>Rabat</span>
1688
+ <span>-{formatPrice(checkout.totalDiscounts.amount)}</span>
1689
+ </div>
1690
+ )}
1691
+
1692
+ {/* Tax */}
1693
+ {checkout?.totalTax && parseFloat(checkout.totalTax.amount) > 0 && (
1694
+ <div className="flex justify-between text-sm">
1695
+ <span className="text-muted-foreground">Podatek VAT</span>
1696
+ <span>{formatPrice(checkout.totalTax.amount)}</span>
1697
+ </div>
1698
+ )}
1699
+
1700
+ {/* Gift Card Deduction */}
1701
+ {checkout?.totalGiftCardAmount && parseFloat(checkout.totalGiftCardAmount.amount) > 0 && (
1702
+ <div className="flex justify-between text-sm text-purple-600">
1703
+ <span>Karta podarunkowa</span>
1704
+ <span>-{formatPrice(checkout.totalGiftCardAmount.amount)}</span>
1705
+ </div>
1706
+ )}
1707
+
1708
+ {/* Total */}
1709
+ <div className="flex justify-between border-t pt-2 text-lg font-semibold">
1710
+ <span>Razem</span>
1711
+ <span>
1712
+ {formatPrice(
1713
+ checkout?.totalPrice?.amount ||
1714
+ items.reduce(
1715
+ (sum, item) => sum + parseFloat(item.price.amount) * item.quantity,
1716
+ 0
1717
+ )
1718
+ )}
1719
+ </span>
1720
+ </div>
1721
+
1722
+ {/* Payment Due (if different from total due to gift cards) */}
1723
+ {checkout?.paymentDue &&
1724
+ checkout?.totalPrice &&
1725
+ parseFloat(checkout.paymentDue.amount) !== parseFloat(checkout.totalPrice.amount) && (
1726
+ <div className="flex justify-between border-t pt-2 text-lg font-bold text-primary">
1727
+ <span>Do zapłaty</span>
1728
+ <span>{formatPrice(checkout.paymentDue.amount)}</span>
1729
+ </div>
1730
+ )}
1731
+ </div>
1732
+
1733
+ {/* Security badges */}
1734
+ <div className="border-t pt-4">
1735
+ <div className="flex items-center justify-center gap-4 text-xs text-muted-foreground">
1736
+ <div className="flex items-center gap-1">
1737
+ <Check className="h-3 w-3" />
1738
+ Bezpieczna płatność
1739
+ </div>
1740
+ <div className="flex items-center gap-1">
1741
+ <Check className="h-3 w-3" />
1742
+ Szyfrowane dane
1743
+ </div>
1744
+ </div>
1745
+ </div>
1746
+ </CardContent>
1747
+ </Card>
1748
+ </div>
1749
+ </div>
1750
+ </div>
1751
+ );
1752
+ }