@doswiftly/cli 0.1.18 → 0.1.20

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 (277) hide show
  1. package/README.md +23 -323
  2. package/dist/commands/check.js +1 -1
  3. package/dist/commands/check.js.map +1 -1
  4. package/dist/commands/deploy.d.ts +20 -0
  5. package/dist/commands/deploy.d.ts.map +1 -1
  6. package/dist/commands/deploy.js +249 -17
  7. package/dist/commands/deploy.js.map +1 -1
  8. package/dist/commands/doctor.js +3 -3
  9. package/dist/commands/doctor.js.map +1 -1
  10. package/dist/commands/init.js +4 -4
  11. package/dist/commands/sdk.js +5 -5
  12. package/dist/commands/sdk.js.map +1 -1
  13. package/dist/commands/template.js +4 -4
  14. package/dist/commands/template.js.map +1 -1
  15. package/dist/commands/types.js +5 -5
  16. package/dist/commands/types.js.map +1 -1
  17. package/dist/commands/verify.js +2 -2
  18. package/dist/commands/verify.js.map +1 -1
  19. package/dist/lib/package-manager.d.ts +1 -1
  20. package/dist/lib/package-manager.js +1 -1
  21. package/package.json +4 -4
  22. package/templates/storefront-minimal/.github/workflows/build-template.yml +10 -0
  23. package/templates/storefront-minimal/wrangler.toml +11 -0
  24. package/templates/storefront-nextjs/.github/workflows/build-template.yml +10 -0
  25. package/templates/storefront-nextjs/README.md +16 -12
  26. package/templates/storefront-nextjs/app/account/orders/page.tsx +2 -2
  27. package/templates/storefront-nextjs/app/account/page.tsx +2 -2
  28. package/templates/storefront-nextjs/app/auth/login/page.tsx +1 -1
  29. package/templates/storefront-nextjs/app/auth/register/page.tsx +1 -1
  30. package/templates/storefront-nextjs/app/cart/page.tsx +1 -1
  31. package/templates/storefront-nextjs/app/categories/[slug]/page.tsx +2 -2
  32. package/templates/storefront-nextjs/app/categories/page.tsx +1 -1
  33. package/templates/storefront-nextjs/app/collections/[slug]/page.tsx +1 -1
  34. package/templates/storefront-nextjs/app/collections/page.tsx +1 -1
  35. package/templates/storefront-nextjs/app/page.tsx +1 -1
  36. package/templates/storefront-nextjs/app/products/[slug]/page.tsx +1 -1
  37. package/templates/storefront-nextjs/app/products/page.tsx +2 -2
  38. package/templates/storefront-nextjs/app/search/page.tsx +1 -1
  39. package/templates/storefront-nextjs/components/auth/auth-guard.tsx +1 -1
  40. package/templates/storefront-nextjs/components/commerce/add-to-cart-button.tsx +1 -1
  41. package/templates/storefront-nextjs/components/commerce/cart-icon.tsx +1 -1
  42. package/templates/storefront-nextjs/components/commerce/currency-selector.tsx +2 -2
  43. package/templates/storefront-nextjs/components/commerce/product-filters.tsx +1 -1
  44. package/templates/storefront-nextjs/components/commerce/product-price.tsx +1 -1
  45. package/templates/storefront-nextjs/components/commerce/search-input.tsx +1 -1
  46. package/templates/storefront-nextjs/components/commerce/sort-select.tsx +1 -1
  47. package/templates/storefront-nextjs/components/providers.tsx +1 -1
  48. package/templates/storefront-nextjs/lib/currency.tsx +3 -3
  49. package/templates/storefront-nextjs/lib/format.ts +1 -1
  50. package/templates/storefront-nextjs/lib/graphql-queries.ts +3 -3
  51. package/templates/storefront-nextjs/package.dev.json +1 -1
  52. package/templates/storefront-nextjs/package.json +1 -1
  53. package/templates/storefront-nextjs/package.json.template +1 -1
  54. package/templates/storefront-nextjs/wrangler.toml +11 -0
  55. package/templates/storefront-nextjs-shadcn/.github/workflows/build-template.yml +10 -0
  56. package/templates/storefront-nextjs-shadcn/.github/workflows/deploy.yml +47 -0
  57. package/templates/storefront-nextjs-shadcn/.github/workflows/preview.yml +47 -0
  58. package/templates/storefront-nextjs-shadcn/CLAUDE.md +172 -35
  59. package/templates/storefront-nextjs-shadcn/README.md +29 -162
  60. package/templates/storefront-nextjs-shadcn/app/{about → [locale]/about}/page.tsx +17 -14
  61. package/templates/storefront-nextjs-shadcn/app/[locale]/account/addresses/page.tsx +226 -0
  62. package/templates/storefront-nextjs-shadcn/app/[locale]/account/error.tsx +46 -0
  63. package/templates/storefront-nextjs-shadcn/app/[locale]/account/loading.tsx +19 -0
  64. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/loyalty/page.tsx +89 -193
  65. package/templates/storefront-nextjs-shadcn/app/[locale]/account/orders/[id]/loading.tsx +60 -0
  66. package/templates/storefront-nextjs-shadcn/app/[locale]/account/orders/[id]/page.tsx +119 -0
  67. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/orders/[id]/tracking/page.tsx +27 -25
  68. package/templates/storefront-nextjs-shadcn/app/[locale]/account/orders/page.tsx +101 -0
  69. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/page.tsx +9 -7
  70. package/templates/storefront-nextjs-shadcn/app/[locale]/account/settings/page.tsx +208 -0
  71. package/templates/storefront-nextjs-shadcn/app/{auth → [locale]/auth}/forgot-password/page.tsx +24 -17
  72. package/templates/storefront-nextjs-shadcn/app/{auth → [locale]/auth}/login/page.tsx +5 -2
  73. package/templates/storefront-nextjs-shadcn/app/{auth → [locale]/auth}/register/page.tsx +5 -2
  74. package/templates/storefront-nextjs-shadcn/app/[locale]/blog/[slug]/loading.tsx +17 -0
  75. package/templates/storefront-nextjs-shadcn/app/{blog → [locale]/blog}/[slug]/page.tsx +44 -3
  76. package/templates/storefront-nextjs-shadcn/app/[locale]/blog/loading.tsx +19 -0
  77. package/templates/storefront-nextjs-shadcn/app/{brands → [locale]/brands}/page.tsx +2 -1
  78. package/templates/storefront-nextjs-shadcn/app/[locale]/cart/loading.tsx +26 -0
  79. package/templates/storefront-nextjs-shadcn/app/{cart → [locale]/cart}/page.tsx +20 -13
  80. package/templates/storefront-nextjs-shadcn/app/[locale]/categories/[slug]/category-products-client.tsx +58 -0
  81. package/templates/storefront-nextjs-shadcn/app/[locale]/categories/[slug]/loading.tsx +32 -0
  82. package/templates/storefront-nextjs-shadcn/app/[locale]/categories/[slug]/page.tsx +95 -0
  83. package/templates/storefront-nextjs-shadcn/app/{categories → [locale]/categories}/page.tsx +21 -12
  84. package/templates/storefront-nextjs-shadcn/app/[locale]/checkout/error.tsx +43 -0
  85. package/templates/storefront-nextjs-shadcn/app/[locale]/checkout/loading.tsx +31 -0
  86. package/templates/storefront-nextjs-shadcn/app/{checkout → [locale]/checkout}/page.tsx +334 -253
  87. package/templates/storefront-nextjs-shadcn/app/{checkout → [locale]/checkout}/success/[orderId]/page.tsx +36 -34
  88. package/templates/storefront-nextjs-shadcn/app/[locale]/collections/[handle]/loading.tsx +19 -0
  89. package/templates/storefront-nextjs-shadcn/app/{collections → [locale]/collections}/[handle]/page.tsx +6 -4
  90. package/templates/storefront-nextjs-shadcn/app/[locale]/collections/loading.tsx +18 -0
  91. package/templates/storefront-nextjs-shadcn/app/{collections → [locale]/collections}/page.tsx +20 -12
  92. package/templates/storefront-nextjs-shadcn/app/{contact → [locale]/contact}/page.tsx +24 -21
  93. package/templates/storefront-nextjs-shadcn/app/{error.tsx → [locale]/error.tsx} +13 -8
  94. package/templates/storefront-nextjs-shadcn/app/[locale]/layout.tsx +92 -0
  95. package/templates/storefront-nextjs-shadcn/app/{not-found.tsx → [locale]/not-found.tsx} +13 -18
  96. package/templates/storefront-nextjs-shadcn/app/{page.tsx → [locale]/page.tsx} +8 -4
  97. package/templates/storefront-nextjs-shadcn/app/[locale]/products/[slug]/error.tsx +43 -0
  98. package/templates/storefront-nextjs-shadcn/app/[locale]/products/[slug]/loading.tsx +29 -0
  99. package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/page.tsx +17 -14
  100. package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/product-client.tsx +18 -62
  101. package/templates/storefront-nextjs-shadcn/app/[locale]/products/loading.tsx +32 -0
  102. package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/page.tsx +6 -3
  103. package/templates/storefront-nextjs-shadcn/app/[locale]/products/products-client.tsx +450 -0
  104. package/templates/storefront-nextjs-shadcn/app/[locale]/search/loading.tsx +18 -0
  105. package/templates/storefront-nextjs-shadcn/app/{wishlist → [locale]/wishlist}/page.tsx +27 -28
  106. package/templates/storefront-nextjs-shadcn/app/api/auth/clear-token/route.ts +2 -86
  107. package/templates/storefront-nextjs-shadcn/app/api/auth/set-token/route.ts +2 -124
  108. package/templates/storefront-nextjs-shadcn/app/global-error.tsx +117 -0
  109. package/templates/storefront-nextjs-shadcn/app/globals.css +8 -0
  110. package/templates/storefront-nextjs-shadcn/app/layout.tsx +8 -35
  111. package/templates/storefront-nextjs-shadcn/codegen.ts +48 -31
  112. package/templates/storefront-nextjs-shadcn/components/account/address-form.tsx +25 -20
  113. package/templates/storefront-nextjs-shadcn/components/account/address-list.tsx +11 -10
  114. package/templates/storefront-nextjs-shadcn/components/account/customer-info.fragment.graphql +36 -0
  115. package/templates/storefront-nextjs-shadcn/components/account/order-details.tsx +17 -13
  116. package/templates/storefront-nextjs-shadcn/components/account/order-history.tsx +42 -30
  117. package/templates/storefront-nextjs-shadcn/components/account/order-summary.fragment.graphql +36 -0
  118. package/templates/storefront-nextjs-shadcn/components/auth/account-menu.tsx +18 -16
  119. package/templates/storefront-nextjs-shadcn/components/auth/login-form.tsx +37 -58
  120. package/templates/storefront-nextjs-shadcn/components/auth/register-form.tsx +85 -66
  121. package/templates/storefront-nextjs-shadcn/components/blog/blog-card.tsx +1 -1
  122. package/templates/storefront-nextjs-shadcn/components/blog/blog-sidebar.tsx +1 -1
  123. package/templates/storefront-nextjs-shadcn/components/brand/brand-card.tsx +1 -1
  124. package/templates/storefront-nextjs-shadcn/components/cart/cart-drawer.tsx +10 -6
  125. package/templates/storefront-nextjs-shadcn/components/cart/cart-icon.tsx +9 -6
  126. package/templates/storefront-nextjs-shadcn/components/cart/cart-item.tsx +8 -6
  127. package/templates/storefront-nextjs-shadcn/components/cart/cart-line.fragment.graphql +53 -0
  128. package/templates/storefront-nextjs-shadcn/components/cart/cart-summary.tsx +10 -8
  129. package/templates/storefront-nextjs-shadcn/components/cart/promo-code-input.tsx +8 -5
  130. package/templates/storefront-nextjs-shadcn/components/cart/shipping-estimator.tsx +38 -20
  131. package/templates/storefront-nextjs-shadcn/components/checkout/payment-method-card.tsx +15 -25
  132. package/templates/storefront-nextjs-shadcn/components/checkout/payment-step.tsx +10 -8
  133. package/templates/storefront-nextjs-shadcn/components/checkout/tax-breakdown.tsx +9 -6
  134. package/templates/storefront-nextjs-shadcn/components/commerce/currency-selector.tsx +7 -5
  135. package/templates/storefront-nextjs-shadcn/components/commerce/pagination.tsx +8 -5
  136. package/templates/storefront-nextjs-shadcn/components/commerce/product-actions.tsx +6 -4
  137. package/templates/storefront-nextjs-shadcn/components/commerce/search-input.tsx +10 -9
  138. package/templates/storefront-nextjs-shadcn/components/common/category-card.tsx +1 -1
  139. package/templates/storefront-nextjs-shadcn/components/common/collection-card.tsx +1 -1
  140. package/templates/storefront-nextjs-shadcn/components/common/price-display.tsx +35 -11
  141. package/templates/storefront-nextjs-shadcn/components/common/social-share.tsx +9 -6
  142. package/templates/storefront-nextjs-shadcn/components/discount/discount-breakdown.tsx +22 -12
  143. package/templates/storefront-nextjs-shadcn/components/discount/discount-code-input.tsx +18 -15
  144. package/templates/storefront-nextjs-shadcn/components/error/error-boundary.tsx +53 -28
  145. package/templates/storefront-nextjs-shadcn/components/filters/dynamic-attribute-filters.tsx +7 -5
  146. package/templates/storefront-nextjs-shadcn/components/filters/range-slider-filter.tsx +5 -5
  147. package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-balance.tsx +19 -15
  148. package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-input.tsx +13 -10
  149. package/templates/storefront-nextjs-shadcn/components/home/category-grid.tsx +10 -6
  150. package/templates/storefront-nextjs-shadcn/components/home/collection-card.fragment.graphql +21 -0
  151. package/templates/storefront-nextjs-shadcn/components/home/featured-collections.tsx +3 -13
  152. package/templates/storefront-nextjs-shadcn/components/home/featured-products.tsx +12 -8
  153. package/templates/storefront-nextjs-shadcn/components/home/hero-section.tsx +13 -8
  154. package/templates/storefront-nextjs-shadcn/components/home/index.ts +0 -1
  155. package/templates/storefront-nextjs-shadcn/components/home/newsletter-signup.tsx +10 -8
  156. package/templates/storefront-nextjs-shadcn/components/hydrated.tsx +24 -0
  157. package/templates/storefront-nextjs-shadcn/components/layout/breadcrumbs.tsx +41 -16
  158. package/templates/storefront-nextjs-shadcn/components/layout/category-node.fragment.graphql +22 -0
  159. package/templates/storefront-nextjs-shadcn/components/layout/currency-selector.tsx +7 -4
  160. package/templates/storefront-nextjs-shadcn/components/layout/footer.tsx +24 -23
  161. package/templates/storefront-nextjs-shadcn/components/layout/header.tsx +52 -34
  162. package/templates/storefront-nextjs-shadcn/components/layout/language-switcher.tsx +54 -0
  163. package/templates/storefront-nextjs-shadcn/components/layout/mobile-menu.tsx +33 -30
  164. package/templates/storefront-nextjs-shadcn/components/layout/navigation.tsx +27 -24
  165. package/templates/storefront-nextjs-shadcn/components/loyalty/points-balance.tsx +2 -11
  166. package/templates/storefront-nextjs-shadcn/components/loyalty/points-history.tsx +8 -25
  167. package/templates/storefront-nextjs-shadcn/components/loyalty/referral-section.tsx +32 -42
  168. package/templates/storefront-nextjs-shadcn/components/loyalty/rewards-catalog.tsx +17 -41
  169. package/templates/storefront-nextjs-shadcn/components/loyalty/tier-progress.tsx +2 -29
  170. package/templates/storefront-nextjs-shadcn/components/order/index.ts +6 -1
  171. package/templates/storefront-nextjs-shadcn/components/product/add-to-cart-button.tsx +6 -14
  172. package/templates/storefront-nextjs-shadcn/components/product/b2b-price-display.tsx +4 -2
  173. package/templates/storefront-nextjs-shadcn/components/product/filter-active-pills.tsx +72 -0
  174. package/templates/storefront-nextjs-shadcn/components/product/filter-mobile-sheet.tsx +87 -0
  175. package/templates/storefront-nextjs-shadcn/components/product/filter-price-range.tsx +140 -0
  176. package/templates/storefront-nextjs-shadcn/components/product/index.ts +9 -2
  177. package/templates/storefront-nextjs-shadcn/components/product/product-card.fragment.graphql +49 -0
  178. package/templates/storefront-nextjs-shadcn/components/product/product-card.tsx +11 -37
  179. package/templates/storefront-nextjs-shadcn/components/product/product-detail.fragment.graphql +52 -0
  180. package/templates/storefront-nextjs-shadcn/components/product/product-filters.tsx +179 -124
  181. package/templates/storefront-nextjs-shadcn/components/product/product-grid.tsx +3 -5
  182. package/templates/storefront-nextjs-shadcn/components/product/product-image.tsx +3 -7
  183. package/templates/storefront-nextjs-shadcn/components/product/product-price.tsx +2 -2
  184. package/templates/storefront-nextjs-shadcn/components/product/product-reviews.tsx +5 -4
  185. package/templates/storefront-nextjs-shadcn/components/product/product-sort.tsx +44 -19
  186. package/templates/storefront-nextjs-shadcn/components/product/product-variant-selector.tsx +8 -23
  187. package/templates/storefront-nextjs-shadcn/components/product/product-variant.fragment.graphql +51 -0
  188. package/templates/storefront-nextjs-shadcn/components/product/review-card.tsx +1 -1
  189. package/templates/storefront-nextjs-shadcn/components/product/review-form.tsx +26 -34
  190. package/templates/storefront-nextjs-shadcn/components/product/savings-display.tsx +17 -2
  191. package/templates/storefront-nextjs-shadcn/components/product/similar-products.tsx +3 -2
  192. package/templates/storefront-nextjs-shadcn/components/providers/index.ts +1 -1
  193. package/templates/storefront-nextjs-shadcn/components/providers/language-sync-provider.tsx +27 -0
  194. package/templates/storefront-nextjs-shadcn/components/providers/stores-provider.tsx +63 -0
  195. package/templates/storefront-nextjs-shadcn/components/providers/theme-provider.tsx +1 -1
  196. package/templates/storefront-nextjs-shadcn/components/returns/index.ts +2 -2
  197. package/templates/storefront-nextjs-shadcn/components/returns/return-request-form.tsx +59 -72
  198. package/templates/storefront-nextjs-shadcn/components/search/search-bar.tsx +7 -4
  199. package/templates/storefront-nextjs-shadcn/components/search/search-results.tsx +3 -2
  200. package/templates/storefront-nextjs-shadcn/components/shipping/shipping-method-selector.tsx +12 -9
  201. package/templates/storefront-nextjs-shadcn/components/ui/empty-state.tsx +23 -12
  202. package/templates/storefront-nextjs-shadcn/components/ui/form.tsx +174 -0
  203. package/templates/storefront-nextjs-shadcn/components/ui/index.ts +30 -2
  204. package/templates/storefront-nextjs-shadcn/components/ui/progress.tsx +40 -0
  205. package/templates/storefront-nextjs-shadcn/components/ui/sheet.tsx +107 -0
  206. package/templates/storefront-nextjs-shadcn/components/ui/slider.tsx +33 -0
  207. package/templates/storefront-nextjs-shadcn/components/ui/textarea.tsx +24 -0
  208. package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-button.tsx +7 -4
  209. package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-icon.tsx +4 -2
  210. package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-item.tsx +2 -10
  211. package/templates/storefront-nextjs-shadcn/generated/graphql.ts +13387 -0
  212. package/templates/storefront-nextjs-shadcn/graphql/custom.example.graphql +159 -0
  213. package/templates/storefront-nextjs-shadcn/hooks/index.ts +3 -0
  214. package/templates/storefront-nextjs-shadcn/hooks/use-auth-sync.ts +42 -0
  215. package/templates/storefront-nextjs-shadcn/hooks/use-auth.ts +17 -295
  216. package/templates/storefront-nextjs-shadcn/hooks/use-cart-actions.ts +34 -229
  217. package/templates/storefront-nextjs-shadcn/hooks/use-cart-di.ts +67 -0
  218. package/templates/storefront-nextjs-shadcn/hooks/use-cart-sync.ts +16 -12
  219. package/templates/storefront-nextjs-shadcn/i18n/navigation.ts +12 -0
  220. package/templates/storefront-nextjs-shadcn/i18n/request.ts +17 -0
  221. package/templates/storefront-nextjs-shadcn/i18n/routing.ts +17 -0
  222. package/templates/storefront-nextjs-shadcn/lib/auth/routes.ts +4 -17
  223. package/templates/storefront-nextjs-shadcn/lib/graphql/client.ts +22 -99
  224. package/templates/storefront-nextjs-shadcn/lib/graphql/config.ts +33 -0
  225. package/templates/storefront-nextjs-shadcn/lib/graphql/fragments.ts +34 -0
  226. package/templates/storefront-nextjs-shadcn/lib/graphql/hooks.ts +720 -632
  227. package/templates/storefront-nextjs-shadcn/lib/graphql/query-keys.ts +88 -0
  228. package/templates/storefront-nextjs-shadcn/lib/graphql/server.ts +132 -182
  229. package/templates/storefront-nextjs-shadcn/lib/graphql/types.ts +62 -0
  230. package/templates/storefront-nextjs-shadcn/lib/theme/theme-config.ts +0 -17
  231. package/templates/storefront-nextjs-shadcn/messages/en.json +869 -0
  232. package/templates/storefront-nextjs-shadcn/messages/pl.json +869 -0
  233. package/templates/storefront-nextjs-shadcn/next-env.d.ts +6 -0
  234. package/templates/storefront-nextjs-shadcn/next.config.ts +6 -5
  235. package/templates/storefront-nextjs-shadcn/package.dev.json +1 -3
  236. package/templates/storefront-nextjs-shadcn/package.json +14 -14
  237. package/templates/storefront-nextjs-shadcn/package.json.template +6 -7
  238. package/templates/storefront-nextjs-shadcn/proxy.ts +115 -47
  239. package/templates/storefront-nextjs-shadcn/stores/cart-store.ts +24 -56
  240. package/templates/storefront-nextjs-shadcn/stores/checkout-store.ts +64 -75
  241. package/templates/storefront-nextjs-shadcn/stores/wishlist-store.ts +178 -177
  242. package/templates/storefront-nextjs-shadcn/tsconfig.json +23 -5
  243. package/templates/storefront-nextjs-shadcn/wrangler.toml +11 -0
  244. package/templates/storefront-nextjs-shadcn/CART_INTEGRATION.md +0 -282
  245. package/templates/storefront-nextjs-shadcn/GRAPHQL_DOCUMENT_NAMES.md +0 -190
  246. package/templates/storefront-nextjs-shadcn/GRAPHQL_ERROR_HANDLING.md +0 -263
  247. package/templates/storefront-nextjs-shadcn/GRAPHQL_FIXES_SUMMARY.md +0 -135
  248. package/templates/storefront-nextjs-shadcn/GRAPHQL_INTEGRATION_COMPLETE.md +0 -142
  249. package/templates/storefront-nextjs-shadcn/INTEGRATION_CHECKLIST.md +0 -448
  250. package/templates/storefront-nextjs-shadcn/PRODUCT_DETAIL_PAGE_IMPLEMENTATION.md +0 -307
  251. package/templates/storefront-nextjs-shadcn/THEME_CUSTOMIZATION.md +0 -245
  252. package/templates/storefront-nextjs-shadcn/app/account/addresses/page.tsx +0 -215
  253. package/templates/storefront-nextjs-shadcn/app/account/orders/[id]/page.tsx +0 -128
  254. package/templates/storefront-nextjs-shadcn/app/account/orders/page.tsx +0 -80
  255. package/templates/storefront-nextjs-shadcn/app/account/settings/page.tsx +0 -171
  256. package/templates/storefront-nextjs-shadcn/app/categories/[slug]/page.tsx +0 -78
  257. package/templates/storefront-nextjs-shadcn/app/products/products-client.tsx +0 -192
  258. package/templates/storefront-nextjs-shadcn/components/providers/currency-provider.tsx +0 -103
  259. package/templates/storefront-nextjs-shadcn/graphql/collections.example.ts +0 -168
  260. package/templates/storefront-nextjs-shadcn/graphql/products.example.ts +0 -160
  261. package/templates/storefront-nextjs-shadcn/lib/auth/cookies.ts +0 -220
  262. package/templates/storefront-nextjs-shadcn/lib/config.ts +0 -46
  263. package/templates/storefront-nextjs-shadcn/lib/currency/IMPLEMENTATION_SUMMARY.md +0 -254
  264. package/templates/storefront-nextjs-shadcn/lib/currency/README.md +0 -464
  265. package/templates/storefront-nextjs-shadcn/lib/currency/cookie-manager.test.ts +0 -328
  266. package/templates/storefront-nextjs-shadcn/lib/currency/cookie-manager.ts +0 -295
  267. package/templates/storefront-nextjs-shadcn/lib/currency/index.ts +0 -27
  268. package/templates/storefront-nextjs-shadcn/lib/format.ts +0 -226
  269. package/templates/storefront-nextjs-shadcn/lib/hooks.ts +0 -30
  270. package/templates/storefront-nextjs-shadcn/stores/auth-store.ts +0 -66
  271. package/templates/storefront-nextjs-shadcn/stores/currency-store.ts +0 -103
  272. /package/templates/storefront-nextjs-shadcn/app/{blog → [locale]/blog}/page.tsx +0 -0
  273. /package/templates/storefront-nextjs-shadcn/app/{brands → [locale]/brands}/[slug]/page.tsx +0 -0
  274. /package/templates/storefront-nextjs-shadcn/app/{returns → [locale]/returns}/page.tsx +0 -0
  275. /package/templates/storefront-nextjs-shadcn/app/{search → [locale]/search}/page.tsx +0 -0
  276. /package/templates/storefront-nextjs-shadcn/app/{search → [locale]/search}/search-client.tsx +0 -0
  277. /package/templates/storefront-nextjs-shadcn/app/{shipping → [locale]/shipping}/page.tsx +0 -0
@@ -0,0 +1,450 @@
1
+ "use client";
2
+
3
+ import { useCallback, useMemo, useTransition } from "react";
4
+ import { useSearchParams } from "next/navigation";
5
+ import { useTranslations } from "next-intl";
6
+ import { useRouter, usePathname } from "@/i18n/navigation";
7
+ import { keepPreviousData } from "@tanstack/react-query";
8
+ import { ProductGrid } from "@/components/product/product-grid";
9
+ import { ProductFilters, type FilterGroup } from "@/components/product/product-filters";
10
+ import { ProductSort, type SortOption } from "@/components/product/product-sort";
11
+ import {
12
+ FilterActivePills,
13
+ type ActivePill,
14
+ } from "@/components/product/filter-active-pills";
15
+ import { FilterMobileSheet } from "@/components/product/filter-mobile-sheet";
16
+ import { Skeleton } from "@/components/ui/skeleton";
17
+ import { Pagination } from "@/components/ui/pagination";
18
+ import { useProducts, useAvailableFilters } from "@/lib/graphql/hooks";
19
+ import type {
20
+ ProductFilterInput,
21
+ AttributeFilterInput,
22
+ } from "@/generated/graphql";
23
+ import { cn } from "@/lib/utils";
24
+
25
+ /**
26
+ * ProductsClient — Dynamic product listing with API-driven filters.
27
+ *
28
+ * UX patterns from BigCommerce Catalyst + Saleor Storefront:
29
+ * - useTransition wraps all filter updates (non-blocking UI)
30
+ * - keepPreviousData prevents skeleton flash on filter changes
31
+ * - data-pending CSS opacity dimming during transitions
32
+ * - Active filter pills with individual removal
33
+ * - Mobile sheet for filters (lg:hidden)
34
+ * - { scroll: false } on all URL updates
35
+ *
36
+ * URL State (SEO-friendly, shareable):
37
+ * ?category=id&collection=id
38
+ * &price_min=100&price_max=500
39
+ * &attr_color=red,blue&attr_size=xl
40
+ * &sort=price-low-to-high&page=2&q=search
41
+ */
42
+ export function ProductsClient() {
43
+ const searchParams = useSearchParams();
44
+ const router = useRouter();
45
+ const pathname = usePathname();
46
+ const [isPending, startTransition] = useTransition();
47
+ const t = useTranslations("product");
48
+ const tFilters = useTranslations("filters");
49
+
50
+ // ========== Parse URL State ==========
51
+ const page = parseInt(searchParams.get("page") || "1", 10);
52
+ const sort = (searchParams.get("sort") as SortOption) || "relevance";
53
+ const searchQuery = searchParams.get("q") || undefined;
54
+ const categoryId = searchParams.get("category") || undefined;
55
+ const collectionId = searchParams.get("collection") || undefined;
56
+ const priceMin = searchParams.get("price_min");
57
+ const priceMax = searchParams.get("price_max");
58
+
59
+ const limit = 20;
60
+
61
+ // Parse dynamic attribute filters: attr_color=red,blue → { attributeId: 'color', values: ['red', 'blue'] }
62
+ const attributeFilters = useMemo(() => {
63
+ const filters: AttributeFilterInput[] = [];
64
+ searchParams.forEach((value: string, key: string) => {
65
+ if (key.startsWith("attr_")) {
66
+ const attributeId = key.slice(5);
67
+ const values = value.split(",").filter(Boolean);
68
+ if (values.length > 0) {
69
+ filters.push({ attributeId, values });
70
+ }
71
+ }
72
+ });
73
+ return filters;
74
+ }, [searchParams]);
75
+
76
+ // ========== Build GraphQL Filter Input ==========
77
+ const filters: ProductFilterInput = useMemo(() => {
78
+ const f: ProductFilterInput = {};
79
+ if (categoryId) f.categoryId = categoryId;
80
+ if (collectionId) f.collectionId = collectionId;
81
+ if (priceMin) f.minPrice = parseFloat(priceMin);
82
+ if (priceMax) f.maxPrice = parseFloat(priceMax);
83
+ if (attributeFilters.length > 0) f.attributes = attributeFilters;
84
+ return f;
85
+ }, [categoryId, collectionId, priceMin, priceMax, attributeFilters]);
86
+
87
+ // ========== Data Fetching (keepPreviousData = no flash) ==========
88
+ const {
89
+ data,
90
+ isLoading: isProductsLoading,
91
+ isFetching: isProductsFetching,
92
+ } = useProducts(
93
+ {
94
+ first: limit,
95
+ query: searchQuery,
96
+ sortKey: sort,
97
+ filters: Object.keys(filters).length > 0 ? filters : undefined,
98
+ },
99
+ { placeholderData: keepPreviousData }
100
+ );
101
+
102
+ const {
103
+ data: filtersData,
104
+ isLoading: isFiltersLoading,
105
+ } = useAvailableFilters(
106
+ {
107
+ categoryId,
108
+ collectionId,
109
+ searchQuery,
110
+ },
111
+ { placeholderData: keepPreviousData }
112
+ );
113
+
114
+ const products = data?.products ?? [];
115
+ const totalCount = data?.totalCount ?? 0;
116
+ const totalPages = Math.ceil(totalCount / limit);
117
+ const availableFilters = filtersData?.availableFilters;
118
+
119
+ // Show dimming when transition or fetch is in progress (but data exists)
120
+ const showDimming = (isPending || isProductsFetching) && products.length > 0;
121
+ // Show skeleton only on initial load (no data yet)
122
+ const showSkeleton = isProductsLoading && products.length === 0;
123
+
124
+ // ========== URL Update (all writes go through here) ==========
125
+ const updateUrl = useCallback(
126
+ (updates: Record<string, string | null>, resetPage = true) => {
127
+ startTransition(() => {
128
+ const newParams = new URLSearchParams(searchParams.toString());
129
+
130
+ for (const [key, value] of Object.entries(updates)) {
131
+ if (value === null || value === "") {
132
+ newParams.delete(key);
133
+ } else {
134
+ newParams.set(key, value);
135
+ }
136
+ }
137
+
138
+ // Reset to page 1 when filters change (not when paginating)
139
+ if (resetPage && !("page" in updates)) {
140
+ newParams.delete("page");
141
+ }
142
+
143
+ const qs = newParams.toString();
144
+ router.push(qs ? `${pathname}?${qs}` : pathname, { scroll: false });
145
+ });
146
+ },
147
+ [searchParams, router, pathname, startTransition]
148
+ );
149
+
150
+ // ========== Filter Groups (from API) ==========
151
+ const filterGroups: FilterGroup[] = useMemo(() => {
152
+ const groups: FilterGroup[] = [];
153
+
154
+ // Categories
155
+ if (availableFilters?.categories && availableFilters.categories.length > 0) {
156
+ groups.push({
157
+ id: "category",
158
+ label: t("category"),
159
+ type: "checkbox",
160
+ options: availableFilters.categories.map((cat) => ({
161
+ label: cat.name,
162
+ value: cat.id,
163
+ count: cat.productCount,
164
+ })),
165
+ });
166
+ }
167
+
168
+ // Price range (from API — real min/max scoped to context)
169
+ if (availableFilters?.priceRange) {
170
+ const minPrice = parseFloat(availableFilters.priceRange.min.amount);
171
+ const maxPrice = parseFloat(availableFilters.priceRange.max.amount);
172
+ if (minPrice !== maxPrice) {
173
+ groups.push({
174
+ id: "price",
175
+ label: t("price"),
176
+ type: "range",
177
+ min: Math.floor(minPrice),
178
+ max: Math.ceil(maxPrice),
179
+ currency: availableFilters.priceRange.min.currencyCode,
180
+ });
181
+ }
182
+ }
183
+
184
+ // Dynamic attribute filters
185
+ if (availableFilters?.attributes) {
186
+ for (const attr of availableFilters.attributes) {
187
+ if (attr.filterValues && attr.filterValues.length > 0) {
188
+ const isColor = attr.type === "COLOR";
189
+ groups.push({
190
+ id: `attr_${attr.handle}`,
191
+ label: attr.name,
192
+ type: isColor ? "color" : "checkbox",
193
+ options: attr.filterValues.map((fv) => ({
194
+ label: fv.label,
195
+ value: fv.value,
196
+ count: fv.productCount,
197
+ colorHex: fv.swatch?.colorHex || undefined,
198
+ })),
199
+ });
200
+ } else if (
201
+ attr.rangeBounds &&
202
+ attr.rangeBounds.min != null &&
203
+ attr.rangeBounds.max != null
204
+ ) {
205
+ groups.push({
206
+ id: `attr_${attr.handle}`,
207
+ label: attr.name,
208
+ type: "range",
209
+ min: attr.rangeBounds.min,
210
+ max: attr.rangeBounds.max,
211
+ });
212
+ }
213
+ }
214
+ }
215
+
216
+ return groups;
217
+ }, [availableFilters, t]);
218
+
219
+ // ========== Selected Filters (from URL) ==========
220
+ const selectedFilters: Record<string, string[]> = useMemo(() => {
221
+ const sel: Record<string, string[]> = {};
222
+
223
+ if (categoryId) {
224
+ sel["category"] = categoryId.split(",").filter(Boolean);
225
+ }
226
+ if (priceMin || priceMax) {
227
+ sel["price"] = [`${priceMin || 0}-${priceMax || 999999}`];
228
+ }
229
+ for (const af of attributeFilters) {
230
+ sel[`attr_${af.attributeId}`] = af.values || [];
231
+ }
232
+
233
+ return sel;
234
+ }, [categoryId, priceMin, priceMax, attributeFilters]);
235
+
236
+ // ========== Active Filter Pills ==========
237
+ const activePills: ActivePill[] = useMemo(() => {
238
+ const pills: ActivePill[] = [];
239
+
240
+ // Category pills
241
+ if (categoryId && availableFilters?.categories) {
242
+ for (const catId of categoryId.split(",").filter(Boolean)) {
243
+ const cat = availableFilters.categories.find((c) => c.id === catId);
244
+ pills.push({
245
+ filterId: "category",
246
+ label: t("category"),
247
+ value: catId,
248
+ displayValue: cat?.name || catId,
249
+ });
250
+ }
251
+ }
252
+
253
+ // Price pill
254
+ if (priceMin || priceMax) {
255
+ const currency =
256
+ availableFilters?.priceRange?.min.currencyCode || "PLN";
257
+ pills.push({
258
+ filterId: "price",
259
+ label: t("price"),
260
+ value: `${priceMin || 0}-${priceMax || "∞"}`,
261
+ displayValue: `${priceMin || 0} — ${priceMax || "∞"} ${currency}`,
262
+ });
263
+ }
264
+
265
+ // Attribute pills
266
+ for (const af of attributeFilters) {
267
+ const attrDef = availableFilters?.attributes?.find(
268
+ (a) => a.handle === af.attributeId
269
+ );
270
+ for (const val of af.values || []) {
271
+ const fv = attrDef?.filterValues?.find((v) => v.value === val);
272
+ pills.push({
273
+ filterId: `attr_${af.attributeId}`,
274
+ label: attrDef?.name || af.attributeId,
275
+ value: val,
276
+ displayValue: fv?.label || val,
277
+ });
278
+ }
279
+ }
280
+
281
+ return pills;
282
+ }, [categoryId, priceMin, priceMax, attributeFilters, availableFilters, t]);
283
+
284
+ const activeFilterCount = activePills.length;
285
+
286
+ // ========== Event Handlers ==========
287
+ const handleFilterChange = useCallback(
288
+ (filterId: string, values: string[]) => {
289
+ if (filterId === "category") {
290
+ updateUrl({ category: values.length > 0 ? values.join(",") : null });
291
+ } else if (filterId === "price") {
292
+ if (values.length > 0) {
293
+ const [min, max] = values[0].split("-");
294
+ updateUrl({
295
+ price_min: min && min !== "0" ? min : null,
296
+ price_max: max && max !== "999999" ? max : null,
297
+ });
298
+ } else {
299
+ updateUrl({ price_min: null, price_max: null });
300
+ }
301
+ } else if (filterId.startsWith("attr_")) {
302
+ updateUrl({ [filterId]: values.length > 0 ? values.join(",") : null });
303
+ }
304
+ },
305
+ [updateUrl]
306
+ );
307
+
308
+ const handleRemovePill = useCallback(
309
+ (filterId: string, value: string) => {
310
+ if (filterId === "category") {
311
+ const remaining = (categoryId || "")
312
+ .split(",")
313
+ .filter((id) => id !== value);
314
+ updateUrl({
315
+ category: remaining.length > 0 ? remaining.join(",") : null,
316
+ });
317
+ } else if (filterId === "price") {
318
+ updateUrl({ price_min: null, price_max: null });
319
+ } else if (filterId.startsWith("attr_")) {
320
+ const attrId = filterId.slice(5);
321
+ const current = searchParams.get(filterId)?.split(",") || [];
322
+ const remaining = current.filter((v) => v !== value);
323
+ updateUrl({
324
+ [filterId]: remaining.length > 0 ? remaining.join(",") : null,
325
+ });
326
+ }
327
+ },
328
+ [categoryId, searchParams, updateUrl]
329
+ );
330
+
331
+ const handleClearAll = useCallback(() => {
332
+ startTransition(() => {
333
+ router.push(pathname, { scroll: false });
334
+ });
335
+ }, [router, pathname, startTransition]);
336
+
337
+ const handleSortChange = useCallback(
338
+ (newSort: SortOption) => {
339
+ updateUrl({ sort: newSort !== "relevance" ? newSort : null });
340
+ },
341
+ [updateUrl]
342
+ );
343
+
344
+ const handlePageChange = useCallback(
345
+ (newPage: number) => {
346
+ updateUrl(
347
+ { page: newPage > 1 ? newPage.toString() : null },
348
+ false
349
+ );
350
+ },
351
+ [updateUrl]
352
+ );
353
+
354
+ // ========== Shared filter props ==========
355
+ const filterProps = {
356
+ filters: filterGroups,
357
+ selectedFilters,
358
+ onFilterChange: handleFilterChange,
359
+ onClearAll: handleClearAll,
360
+ };
361
+
362
+ return (
363
+ <div className="flex flex-col gap-6">
364
+ {/* Top bar: Mobile filter trigger + Sort + Result count */}
365
+ <div className="flex flex-wrap items-center justify-between gap-3">
366
+ <div className="flex items-center gap-3">
367
+ {/* Mobile filter sheet trigger */}
368
+ <FilterMobileSheet
369
+ {...filterProps}
370
+ activeFilterCount={activeFilterCount}
371
+ totalProducts={availableFilters?.totalProducts}
372
+ />
373
+ <p className="text-sm text-muted-foreground">
374
+ {totalCount > 0
375
+ ? tFilters("productCount", { count: totalCount })
376
+ : t("noProducts")}
377
+ </p>
378
+ </div>
379
+ <ProductSort value={sort} onChange={handleSortChange} />
380
+ </div>
381
+
382
+ {/* Active filter pills */}
383
+ <FilterActivePills
384
+ pills={activePills}
385
+ onRemove={handleRemovePill}
386
+ onClearAll={handleClearAll}
387
+ />
388
+
389
+ {/* Main layout: Sidebar + Grid */}
390
+ <div className="flex gap-8">
391
+ {/* Desktop sidebar (hidden on mobile — sheet used instead) */}
392
+ <aside className="hidden w-64 flex-shrink-0 lg:block">
393
+ <div className="sticky top-4">
394
+ {isFiltersLoading && !availableFilters ? (
395
+ <div className="space-y-4">
396
+ {Array.from({ length: 3 }).map((_, i) => (
397
+ <Skeleton key={i} className="h-24 w-full" />
398
+ ))}
399
+ </div>
400
+ ) : (
401
+ <ProductFilters {...filterProps} />
402
+ )}
403
+ </div>
404
+ </aside>
405
+
406
+ {/* Products grid with dimming */}
407
+ <div className="flex-1">
408
+ <div
409
+ className={cn(
410
+ "transition-opacity duration-200",
411
+ showDimming && "pointer-events-none opacity-50"
412
+ )}
413
+ data-pending={showDimming || undefined}
414
+ aria-busy={showDimming}
415
+ role="region"
416
+ aria-label="Product results"
417
+ >
418
+ {showSkeleton ? (
419
+ <div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
420
+ {Array.from({ length: 6 }).map((_, i) => (
421
+ <Skeleton key={i} className="aspect-square w-full" />
422
+ ))}
423
+ </div>
424
+ ) : (
425
+ <ProductGrid
426
+ products={products}
427
+ columns={3}
428
+ priorityCount={6}
429
+ showBadges
430
+ emptyMessage={t("noProductsMatch")}
431
+ onResetFilters={handleClearAll}
432
+ />
433
+ )}
434
+ </div>
435
+
436
+ {/* Pagination */}
437
+ {totalPages > 1 && (
438
+ <div className="mt-8 flex justify-center">
439
+ <Pagination
440
+ currentPage={page}
441
+ totalPages={totalPages}
442
+ onPageChange={handlePageChange}
443
+ />
444
+ </div>
445
+ )}
446
+ </div>
447
+ </div>
448
+ </div>
449
+ );
450
+ }
@@ -0,0 +1,18 @@
1
+ import { Skeleton } from "@/components/ui/skeleton";
2
+
3
+ export default function SearchLoading() {
4
+ return (
5
+ <div className="container mx-auto px-4 py-8">
6
+ <Skeleton className="mb-8 h-12 w-full max-w-xl" />
7
+ <div className="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4">
8
+ {[...Array(8)].map((_, i) => (
9
+ <div key={i} className="space-y-3">
10
+ <Skeleton className="aspect-square w-full rounded-lg" />
11
+ <Skeleton className="h-4 w-3/4" />
12
+ <Skeleton className="h-4 w-1/2" />
13
+ </div>
14
+ ))}
15
+ </div>
16
+ </div>
17
+ );
18
+ }
@@ -7,6 +7,8 @@
7
7
  * and quick add to cart functionality.
8
8
  */
9
9
 
10
+ import { useTranslations } from 'next-intl';
11
+ import { Link } from '@/i18n/navigation';
10
12
  import { Heart, ShoppingCart, Trash2 } from 'lucide-react';
11
13
  import { Button } from '@/components/ui/button';
12
14
  import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
@@ -14,16 +16,18 @@ import { EmptyState } from '@/components/ui/empty-state';
14
16
  import { Breadcrumbs } from '@/components/layout/breadcrumbs';
15
17
  import { WishlistItem } from '@/components/wishlist/wishlist-item';
16
18
  import { useWishlistStore } from '@/stores/wishlist-store';
19
+ import { useHydrated } from '@doswiftly/storefront-sdk/react';
17
20
  import { useCartActions } from '@/hooks/use-cart-actions';
18
21
  import { toast } from 'sonner';
19
22
 
20
23
  export default function WishlistPage() {
24
+ const t = useTranslations('wishlist');
21
25
  const {
22
26
  wishlists,
23
27
  getActiveWishlist,
24
28
  clearWishlist,
25
- isHydrated,
26
29
  } = useWishlistStore();
30
+ const isHydrated = useHydrated();
27
31
 
28
32
  const { addToCart } = useCartActions();
29
33
 
@@ -33,7 +37,7 @@ export default function WishlistPage() {
33
37
  const handleClearWishlist = () => {
34
38
  if (activeWishlist) {
35
39
  clearWishlist(activeWishlist.id);
36
- toast.success('Lista życzeń została wyczyszczona');
40
+ toast.success(t('cleared'));
37
41
  }
38
42
  };
39
43
 
@@ -42,21 +46,13 @@ export default function WishlistPage() {
42
46
 
43
47
  for (const item of items) {
44
48
  try {
45
- await addToCart({
46
- variantId: item.variantId || item.productId,
47
- productId: item.productId,
48
- productTitle: item.productTitle,
49
- variantTitle: item.variantTitle || '',
50
- price: item.price,
51
- image: item.image,
52
- available: true,
53
- });
49
+ await addToCart(item.variantId || item.productId);
54
50
  } catch {
55
51
  // Individual item errors are handled by addToCart (shows toast)
56
52
  }
57
53
  }
58
54
 
59
- toast.success(`Dodano ${items.length} produktów do koszyka`);
55
+ toast.success(t('addedToCart', { count: items.length }));
60
56
  };
61
57
 
62
58
  // Show loading state while hydrating from localStorage
@@ -76,8 +72,8 @@ export default function WishlistPage() {
76
72
  <div className="container max-w-4xl py-8">
77
73
  <Breadcrumbs
78
74
  items={[
79
- { label: 'Strona główna', href: '/' },
80
- { label: 'Lista życzeń' },
75
+ { label: t('home'), href: '/' },
76
+ { label: t('title') },
81
77
  ]}
82
78
  />
83
79
 
@@ -85,12 +81,12 @@ export default function WishlistPage() {
85
81
  <div>
86
82
  <h1 className="text-3xl font-bold tracking-tight flex items-center gap-3">
87
83
  <Heart className="h-8 w-8 text-red-500" />
88
- Lista życzeń
84
+ {t('title')}
89
85
  </h1>
90
86
  <p className="text-muted-foreground mt-1">
91
87
  {items.length === 0
92
- ? 'Twoja lista życzeń jest pusta'
93
- : `${items.length} ${items.length === 1 ? 'produkt' : 'produktów'}`}
88
+ ? t('empty')
89
+ : t('itemCount', { count: items.length })}
94
90
  </p>
95
91
  </div>
96
92
 
@@ -98,11 +94,11 @@ export default function WishlistPage() {
98
94
  <div className="flex gap-2">
99
95
  <Button variant="outline" size="sm" onClick={handleClearWishlist}>
100
96
  <Trash2 className="h-4 w-4 mr-2" />
101
- Wyczyść
97
+ {t('clearList')}
102
98
  </Button>
103
99
  <Button size="sm" onClick={handleAddAllToCart}>
104
100
  <ShoppingCart className="h-4 w-4 mr-2" />
105
- Dodaj wszystko do koszyka
101
+ {t('addAllToCart')}
106
102
  </Button>
107
103
  </div>
108
104
  )}
@@ -111,12 +107,13 @@ export default function WishlistPage() {
111
107
  {items.length === 0 ? (
112
108
  <EmptyState
113
109
  icon={<Heart className="h-12 w-12" />}
114
- title="Lista życzeń jest pusta"
115
- description="Dodaj produkty do listy życzeń, aby śledzić ich ceny i szybko dodawać do koszyka."
116
- action={{
117
- label: 'Przeglądaj produkty',
118
- href: '/products',
119
- }}
110
+ title={t('empty')}
111
+ description={t('emptyDescription')}
112
+ action={
113
+ <Link href="/products">
114
+ <Button>{t('browseProducts')}</Button>
115
+ </Link>
116
+ }
120
117
  />
121
118
  ) : (
122
119
  <div className="space-y-4">
@@ -141,6 +138,8 @@ export default function WishlistPage() {
141
138
  * Shows summary of price changes for all items
142
139
  */
143
140
  function PriceChangeSummary({ items }: { items: Array<{ price: { amount: string }; priceAtAdd: { amount: string } }> }) {
141
+ const t = useTranslations('wishlist');
142
+
144
143
  const itemsWithPriceDrops = items.filter((item) => {
145
144
  const current = parseFloat(item.price.amount);
146
145
  const original = parseFloat(item.priceAtAdd.amount);
@@ -159,13 +158,13 @@ function PriceChangeSummary({ items }: { items: Array<{ price: { amount: string
159
158
  <Card className="border-green-200 bg-green-50 dark:border-green-900 dark:bg-green-950">
160
159
  <CardHeader className="pb-2">
161
160
  <CardTitle className="text-green-700 dark:text-green-400 text-lg flex items-center gap-2">
162
- 🎉 Dobre wieści!
161
+ {t('goodNews')}
163
162
  </CardTitle>
164
163
  </CardHeader>
165
164
  <CardContent>
166
165
  <p className="text-green-700 dark:text-green-400">
167
- {itemsWithPriceDrops.length} {itemsWithPriceDrops.length === 1 ? 'produkt obniżył' : 'produktów obniżyło'} cenę!
168
- Możesz zaoszczędzić łącznie{' '}
166
+ {t('priceDropped', { count: itemsWithPriceDrops.length })}{' '}
167
+ {t('totalSavings')}{' '}
169
168
  <span className="font-semibold">
170
169
  {new Intl.NumberFormat('pl-PL', {
171
170
  style: 'currency',