@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,173 @@
1
+ /**
2
+ * Tests for useFilterParams hook
3
+ *
4
+ * Requirements: R35.20, R35.21, R35.22
5
+ */
6
+
7
+ // Note: This is a test specification file. In a real test setup, you would use
8
+ // @testing-library/react-hooks and mock next/navigation.
9
+
10
+ import type { AppliedFilters } from "@/components/filters/dynamic-attribute-filters";
11
+
12
+ /**
13
+ * URL Parameter Parsing Tests (R35.20)
14
+ */
15
+ describe("URL Parameter Parsing", () => {
16
+ describe("parseSearchParams", () => {
17
+ it("should parse discrete attribute filters from URL", () => {
18
+ // URL: ?attr_color=red,blue
19
+ const params = new URLSearchParams("attr_color=red,blue");
20
+ const expected: Partial<AppliedFilters> = {
21
+ attributes: { color: ["red", "blue"] },
22
+ };
23
+ // Implementation would parse this
24
+ });
25
+
26
+ it("should parse multiple attribute filters", () => {
27
+ // URL: ?attr_color=red&attr_size=xl,xxl
28
+ const params = new URLSearchParams("attr_color=red&attr_size=xl,xxl");
29
+ const expected: Partial<AppliedFilters> = {
30
+ attributes: {
31
+ color: ["red"],
32
+ size: ["xl", "xxl"],
33
+ },
34
+ };
35
+ });
36
+
37
+ it("should parse range attribute filters", () => {
38
+ // URL: ?attr_weight_min=100&attr_weight_max=500
39
+ const params = new URLSearchParams("attr_weight_min=100&attr_weight_max=500");
40
+ const expected: Partial<AppliedFilters> = {
41
+ ranges: { weight: { min: 100, max: 500 } },
42
+ };
43
+ });
44
+
45
+ it("should parse price filters", () => {
46
+ // URL: ?min_price=50&max_price=200
47
+ const params = new URLSearchParams("min_price=50&max_price=200");
48
+ const expected: Partial<AppliedFilters> = {
49
+ price: { min: 50, max: 200 },
50
+ };
51
+ });
52
+
53
+ it("should parse category filter", () => {
54
+ // URL: ?category=category-id
55
+ const params = new URLSearchParams("category=category-id");
56
+ const expected: Partial<AppliedFilters> = {
57
+ categoryId: "category-id",
58
+ };
59
+ });
60
+
61
+ it("should handle empty values", () => {
62
+ // URL: ?attr_color=
63
+ const params = new URLSearchParams("attr_color=");
64
+ const expected: Partial<AppliedFilters> = {
65
+ attributes: {},
66
+ };
67
+ });
68
+ });
69
+ });
70
+
71
+ /**
72
+ * URL Serialization Tests (R35.21)
73
+ */
74
+ describe("URL Serialization", () => {
75
+ describe("serializeToSearchParams", () => {
76
+ it("should serialize discrete attribute filters", () => {
77
+ const filters: AppliedFilters = {
78
+ attributes: { color: ["red", "blue"] },
79
+ ranges: {},
80
+ };
81
+ // Expected URL: ?attr_color=red,blue
82
+ });
83
+
84
+ it("should serialize range attribute filters", () => {
85
+ const filters: AppliedFilters = {
86
+ attributes: {},
87
+ ranges: { weight: { min: 100, max: 500 } },
88
+ };
89
+ // Expected URL: ?attr_weight_min=100&attr_weight_max=500
90
+ });
91
+
92
+ it("should serialize price filters", () => {
93
+ const filters: AppliedFilters = {
94
+ attributes: {},
95
+ ranges: {},
96
+ price: { min: 50, max: 200 },
97
+ };
98
+ // Expected URL: ?min_price=50&max_price=200
99
+ });
100
+
101
+ it("should preserve non-filter params", () => {
102
+ const filters: AppliedFilters = {
103
+ attributes: { color: ["red"] },
104
+ ranges: {},
105
+ };
106
+ const existingParams = new URLSearchParams("page=2&sort=price");
107
+ // Expected URL: ?page=2&sort=price&attr_color=red
108
+ });
109
+
110
+ it("should omit empty filters", () => {
111
+ const filters: AppliedFilters = {
112
+ attributes: { color: [] },
113
+ ranges: { weight: {} },
114
+ };
115
+ // Expected URL: (empty or just non-filter params)
116
+ });
117
+ });
118
+ });
119
+
120
+ /**
121
+ * Clear Filters Tests (R35.22)
122
+ */
123
+ describe("Clear Filters", () => {
124
+ it("should clear single attribute filter", () => {
125
+ const initial: AppliedFilters = {
126
+ attributes: { color: ["red"], size: ["xl"] },
127
+ ranges: {},
128
+ };
129
+ // After clearing color:
130
+ const expected: AppliedFilters = {
131
+ attributes: { size: ["xl"] },
132
+ ranges: {},
133
+ };
134
+ });
135
+
136
+ it("should clear all filters", () => {
137
+ const initial: AppliedFilters = {
138
+ attributes: { color: ["red"], size: ["xl"] },
139
+ ranges: { weight: { min: 100, max: 500 } },
140
+ price: { min: 50, max: 200 },
141
+ categoryId: "category-1",
142
+ };
143
+ const expected: AppliedFilters = {
144
+ attributes: {},
145
+ ranges: {},
146
+ price: undefined,
147
+ categoryId: undefined,
148
+ };
149
+ });
150
+
151
+ it("should maintain non-attribute params when clearing", () => {
152
+ // URL: ?page=2&sort=price&attr_color=red
153
+ // After clearAllFilters:
154
+ // URL: ?page=2&sort=price
155
+ });
156
+ });
157
+
158
+ /**
159
+ * Debounce Tests
160
+ */
161
+ describe("Debounce", () => {
162
+ it("should debounce URL updates", () => {
163
+ // Multiple rapid filter changes should result in single URL update
164
+ // after debounce delay (300ms default)
165
+ });
166
+
167
+ it("should use custom debounce delay if provided", () => {
168
+ // With debounceMs: 500, updates should wait 500ms
169
+ });
170
+ });
171
+
172
+ // Export for test runner
173
+ export {};
@@ -0,0 +1,298 @@
1
+ "use client";
2
+
3
+ import { useCallback, useMemo, useRef, useEffect } from "react";
4
+ import { useSearchParams, useRouter, usePathname } from "next/navigation";
5
+ import type { AppliedFilters } from "@/components/filters/dynamic-attribute-filters";
6
+
7
+ /**
8
+ * useFilterParams - Hook for syncing filter state with URL
9
+ *
10
+ * URL parameter format:
11
+ * - Discrete attributes: ?attr_color=red,blue&attr_size=xl
12
+ * - Range attributes: ?attr_price_min=100&attr_price_max=500
13
+ * - Price filter: ?min_price=50&max_price=200
14
+ * - Category: ?category=category-id
15
+ *
16
+ * Features:
17
+ * - Debounced URL updates (300ms)
18
+ * - Multi-value support (comma-separated)
19
+ * - Range value support (min/max pairs)
20
+ * - SSR-safe (uses useSearchParams)
21
+ *
22
+ * Requirements: R35.20, R35.21, R35.22
23
+ */
24
+
25
+ const ATTR_PREFIX = "attr_";
26
+ const RANGE_MIN_SUFFIX = "_min";
27
+ const RANGE_MAX_SUFFIX = "_max";
28
+ const DEBOUNCE_MS = 300;
29
+
30
+ export interface UseFilterParamsOptions {
31
+ /** Debounce delay in ms (default: 300) */
32
+ debounceMs?: number;
33
+ }
34
+
35
+ export interface UseFilterParamsReturn {
36
+ /** Current applied filters parsed from URL */
37
+ appliedFilters: AppliedFilters;
38
+ /** Update attribute filter values */
39
+ setAttributeFilter: (attributeId: string, values: string[]) => void;
40
+ /** Update range filter values */
41
+ setRangeFilter: (attributeId: string, range: { min?: number; max?: number }) => void;
42
+ /** Update price filter */
43
+ setPriceFilter: (range: { min?: number; max?: number }) => void;
44
+ /** Update category filter */
45
+ setCategoryFilter: (categoryId?: string) => void;
46
+ /** Clear all filters */
47
+ clearAllFilters: () => void;
48
+ /** Get URL for sharing/linking with current filters */
49
+ getFilteredUrl: () => string;
50
+ }
51
+
52
+ /**
53
+ * Parse URL search params into AppliedFilters object
54
+ */
55
+ function parseSearchParams(searchParams: URLSearchParams): AppliedFilters {
56
+ const attributes: Record<string, string[]> = {};
57
+ const ranges: Record<string, { min?: number; max?: number }> = {};
58
+ let price: { min?: number; max?: number } | undefined;
59
+ let categoryId: string | undefined;
60
+
61
+ // Parse category
62
+ const categoryParam = searchParams.get("category");
63
+ if (categoryParam) {
64
+ categoryId = categoryParam;
65
+ }
66
+
67
+ // Parse price
68
+ const minPrice = searchParams.get("min_price");
69
+ const maxPrice = searchParams.get("max_price");
70
+ if (minPrice || maxPrice) {
71
+ price = {
72
+ min: minPrice ? parseFloat(minPrice) : undefined,
73
+ max: maxPrice ? parseFloat(maxPrice) : undefined,
74
+ };
75
+ }
76
+
77
+ // Parse attribute filters
78
+ for (const [key, value] of searchParams.entries()) {
79
+ if (key.startsWith(ATTR_PREFIX)) {
80
+ const attrKey = key.slice(ATTR_PREFIX.length);
81
+
82
+ // Check if it's a range parameter (ends with _min or _max)
83
+ if (attrKey.endsWith(RANGE_MIN_SUFFIX)) {
84
+ const attrId = attrKey.slice(0, -RANGE_MIN_SUFFIX.length);
85
+ const numValue = parseFloat(value);
86
+ if (!isNaN(numValue)) {
87
+ if (!ranges[attrId]) ranges[attrId] = {};
88
+ ranges[attrId].min = numValue;
89
+ }
90
+ } else if (attrKey.endsWith(RANGE_MAX_SUFFIX)) {
91
+ const attrId = attrKey.slice(0, -RANGE_MAX_SUFFIX.length);
92
+ const numValue = parseFloat(value);
93
+ if (!isNaN(numValue)) {
94
+ if (!ranges[attrId]) ranges[attrId] = {};
95
+ ranges[attrId].max = numValue;
96
+ }
97
+ } else {
98
+ // Discrete attribute values (comma-separated)
99
+ const values = value.split(",").filter(Boolean);
100
+ if (values.length > 0) {
101
+ attributes[attrKey] = values;
102
+ }
103
+ }
104
+ }
105
+ }
106
+
107
+ return { attributes, ranges, price, categoryId };
108
+ }
109
+
110
+ /**
111
+ * Serialize AppliedFilters to URLSearchParams
112
+ */
113
+ function serializeToSearchParams(
114
+ filters: AppliedFilters,
115
+ existingParams?: URLSearchParams
116
+ ): URLSearchParams {
117
+ const params = new URLSearchParams();
118
+
119
+ // Preserve non-filter params (like page, sort)
120
+ if (existingParams) {
121
+ for (const [key, value] of existingParams.entries()) {
122
+ if (
123
+ !key.startsWith(ATTR_PREFIX) &&
124
+ key !== "min_price" &&
125
+ key !== "max_price" &&
126
+ key !== "category"
127
+ ) {
128
+ params.set(key, value);
129
+ }
130
+ }
131
+ }
132
+
133
+ // Category
134
+ if (filters.categoryId) {
135
+ params.set("category", filters.categoryId);
136
+ }
137
+
138
+ // Price
139
+ if (filters.price?.min !== undefined) {
140
+ params.set("min_price", filters.price.min.toString());
141
+ }
142
+ if (filters.price?.max !== undefined) {
143
+ params.set("max_price", filters.price.max.toString());
144
+ }
145
+
146
+ // Discrete attributes
147
+ for (const [attrId, values] of Object.entries(filters.attributes)) {
148
+ if (values.length > 0) {
149
+ params.set(`${ATTR_PREFIX}${attrId}`, values.join(","));
150
+ }
151
+ }
152
+
153
+ // Range attributes
154
+ for (const [attrId, range] of Object.entries(filters.ranges)) {
155
+ if (range.min !== undefined) {
156
+ params.set(`${ATTR_PREFIX}${attrId}${RANGE_MIN_SUFFIX}`, range.min.toString());
157
+ }
158
+ if (range.max !== undefined) {
159
+ params.set(`${ATTR_PREFIX}${attrId}${RANGE_MAX_SUFFIX}`, range.max.toString());
160
+ }
161
+ }
162
+
163
+ return params;
164
+ }
165
+
166
+ export function useFilterParams(
167
+ options: UseFilterParamsOptions = {}
168
+ ): UseFilterParamsReturn {
169
+ const { debounceMs = DEBOUNCE_MS } = options;
170
+ const router = useRouter();
171
+ const pathname = usePathname();
172
+ const searchParams = useSearchParams();
173
+
174
+ // Debounce timer ref
175
+ const debounceRef = useRef<NodeJS.Timeout | null>(null);
176
+
177
+ // Parse current filters from URL
178
+ const appliedFilters = useMemo(() => {
179
+ return parseSearchParams(searchParams);
180
+ }, [searchParams]);
181
+
182
+ // Update URL with debouncing
183
+ const updateUrl = useCallback(
184
+ (newFilters: AppliedFilters) => {
185
+ if (debounceRef.current) {
186
+ clearTimeout(debounceRef.current);
187
+ }
188
+
189
+ debounceRef.current = setTimeout(() => {
190
+ const params = serializeToSearchParams(newFilters, searchParams);
191
+ const queryString = params.toString();
192
+ const newUrl = queryString ? `${pathname}?${queryString}` : pathname;
193
+ router.push(newUrl, { scroll: false });
194
+ }, debounceMs);
195
+ },
196
+ [pathname, searchParams, router, debounceMs]
197
+ );
198
+
199
+ // Cleanup on unmount
200
+ useEffect(() => {
201
+ return () => {
202
+ if (debounceRef.current) {
203
+ clearTimeout(debounceRef.current);
204
+ }
205
+ };
206
+ }, []);
207
+
208
+ // Set attribute filter values
209
+ const setAttributeFilter = useCallback(
210
+ (attributeId: string, values: string[]) => {
211
+ const newFilters = { ...appliedFilters };
212
+ newFilters.attributes = { ...newFilters.attributes };
213
+
214
+ if (values.length > 0) {
215
+ newFilters.attributes[attributeId] = values;
216
+ } else {
217
+ delete newFilters.attributes[attributeId];
218
+ }
219
+
220
+ updateUrl(newFilters);
221
+ },
222
+ [appliedFilters, updateUrl]
223
+ );
224
+
225
+ // Set range filter values
226
+ const setRangeFilter = useCallback(
227
+ (attributeId: string, range: { min?: number; max?: number }) => {
228
+ const newFilters = { ...appliedFilters };
229
+ newFilters.ranges = { ...newFilters.ranges };
230
+
231
+ if (range.min !== undefined || range.max !== undefined) {
232
+ newFilters.ranges[attributeId] = range;
233
+ } else {
234
+ delete newFilters.ranges[attributeId];
235
+ }
236
+
237
+ updateUrl(newFilters);
238
+ },
239
+ [appliedFilters, updateUrl]
240
+ );
241
+
242
+ // Set price filter
243
+ const setPriceFilter = useCallback(
244
+ (range: { min?: number; max?: number }) => {
245
+ const newFilters = { ...appliedFilters };
246
+
247
+ if (range.min !== undefined || range.max !== undefined) {
248
+ newFilters.price = range;
249
+ } else {
250
+ newFilters.price = undefined;
251
+ }
252
+
253
+ updateUrl(newFilters);
254
+ },
255
+ [appliedFilters, updateUrl]
256
+ );
257
+
258
+ // Set category filter
259
+ const setCategoryFilter = useCallback(
260
+ (categoryId?: string) => {
261
+ const newFilters = { ...appliedFilters };
262
+ newFilters.categoryId = categoryId;
263
+ updateUrl(newFilters);
264
+ },
265
+ [appliedFilters, updateUrl]
266
+ );
267
+
268
+ // Clear all filters
269
+ const clearAllFilters = useCallback(() => {
270
+ const newFilters: AppliedFilters = {
271
+ attributes: {},
272
+ ranges: {},
273
+ price: undefined,
274
+ categoryId: undefined,
275
+ };
276
+ updateUrl(newFilters);
277
+ }, [updateUrl]);
278
+
279
+ // Get URL for sharing
280
+ const getFilteredUrl = useCallback(() => {
281
+ const params = serializeToSearchParams(appliedFilters, searchParams);
282
+ const queryString = params.toString();
283
+ const baseUrl = typeof window !== "undefined" ? window.location.origin : "";
284
+ return queryString ? `${baseUrl}${pathname}?${queryString}` : `${baseUrl}${pathname}`;
285
+ }, [appliedFilters, pathname, searchParams]);
286
+
287
+ return {
288
+ appliedFilters,
289
+ setAttributeFilter,
290
+ setRangeFilter,
291
+ setPriceFilter,
292
+ setCategoryFilter,
293
+ clearAllFilters,
294
+ getFilteredUrl,
295
+ };
296
+ }
297
+
298
+ export default useFilterParams;
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Cookie Helpers for Authentication
3
+ *
4
+ * Provides utilities for managing authentication cookies in both
5
+ * client-side and server-side contexts.
6
+ *
7
+ * Security Notes:
8
+ * - Cookies are httpOnly (set via API routes, not client-side)
9
+ * - Cookies are secure in production (HTTPS only)
10
+ * - Cookies are SameSite=Lax to prevent CSRF
11
+ * - Token validation happens on GraphQL backend
12
+ *
13
+ * @see lib/auth/routes.ts - Cookie name constant
14
+ * @see app/api/auth/set-token/route.ts - Server-side cookie setter
15
+ * @see app/api/auth/clear-token/route.ts - Server-side cookie clearer
16
+ */
17
+
18
+ import { AUTH_COOKIE_NAME } from "./routes";
19
+
20
+ /**
21
+ * Cookie configuration for authentication token
22
+ */
23
+ export const AUTH_COOKIE_CONFIG = {
24
+ name: AUTH_COOKIE_NAME,
25
+ maxAge: 60 * 60 * 24 * 30, // 30 days (in seconds)
26
+ path: "/",
27
+ sameSite: "lax" as const,
28
+ secure: process.env.NODE_ENV === "production",
29
+ httpOnly: true, // Cannot be accessed via JavaScript (security)
30
+ } as const;
31
+
32
+ /**
33
+ * Get authentication token from cookies (client-side)
34
+ *
35
+ * Note: This only works if the cookie is NOT httpOnly.
36
+ * For httpOnly cookies, use server-side methods or API routes.
37
+ *
38
+ * @returns Token string or null if not found
39
+ *
40
+ * @example
41
+ * ```tsx
42
+ * const token = getAuthToken();
43
+ * if (token) {
44
+ * // User is authenticated
45
+ * }
46
+ * ```
47
+ */
48
+ export function getAuthToken(): string | null {
49
+ if (typeof window === "undefined") {
50
+ return null; // Server-side
51
+ }
52
+
53
+ const cookies = document.cookie.split("; ");
54
+ const authCookie = cookies.find((c) => c.startsWith(`${AUTH_COOKIE_NAME}=`));
55
+
56
+ if (!authCookie) {
57
+ return null;
58
+ }
59
+
60
+ return authCookie.split("=")[1] || null;
61
+ }
62
+
63
+ /**
64
+ * Check if user is authenticated (client-side)
65
+ *
66
+ * Note: This checks cookie EXISTENCE, not VALIDITY.
67
+ * Token validation happens on the GraphQL backend.
68
+ *
69
+ * @returns True if auth cookie exists
70
+ *
71
+ * @example
72
+ * ```tsx
73
+ * const isAuthenticated = isAuthTokenPresent();
74
+ * if (isAuthenticated) {
75
+ * // Show authenticated UI
76
+ * }
77
+ * ```
78
+ */
79
+ export function isAuthTokenPresent(): boolean {
80
+ return getAuthToken() !== null;
81
+ }
82
+
83
+ /**
84
+ * Set authentication token (via API route)
85
+ *
86
+ * This function calls the API route to set an httpOnly cookie.
87
+ * Direct client-side cookie setting is NOT secure for auth tokens.
88
+ *
89
+ * @param token - Customer access token from GraphQL
90
+ * @returns Promise that resolves when cookie is set
91
+ *
92
+ * @example
93
+ * ```tsx
94
+ * const { customerAccessToken } = await loginMutation.mutateAsync({
95
+ * input: { email, password }
96
+ * });
97
+ *
98
+ * if (customerAccessToken) {
99
+ * await setAuthToken(customerAccessToken.accessToken);
100
+ * }
101
+ * ```
102
+ */
103
+ export async function setAuthToken(token: string): Promise<void> {
104
+ const response = await fetch("/api/auth/set-token", {
105
+ method: "POST",
106
+ headers: {
107
+ "Content-Type": "application/json",
108
+ },
109
+ body: JSON.stringify({ token }),
110
+ });
111
+
112
+ if (!response.ok) {
113
+ throw new Error("Failed to set authentication token");
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Clear authentication token (via API route)
119
+ *
120
+ * This function calls the API route to clear the httpOnly cookie.
121
+ *
122
+ * @returns Promise that resolves when cookie is cleared
123
+ *
124
+ * @example
125
+ * ```tsx
126
+ * await clearAuthToken();
127
+ * router.push('/auth/login');
128
+ * ```
129
+ */
130
+ export async function clearAuthToken(): Promise<void> {
131
+ const response = await fetch("/api/auth/clear-token", {
132
+ method: "POST",
133
+ });
134
+
135
+ if (!response.ok) {
136
+ throw new Error("Failed to clear authentication token");
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Parse cookie string into key-value object
142
+ *
143
+ * Utility function for parsing cookie strings in server-side contexts.
144
+ *
145
+ * @param cookieString - Raw cookie string from request headers
146
+ * @returns Object with cookie key-value pairs
147
+ *
148
+ * @example
149
+ * ```tsx
150
+ * const cookies = parseCookies(request.headers.get('cookie') || '');
151
+ * const token = cookies[AUTH_COOKIE_NAME];
152
+ * ```
153
+ */
154
+ export function parseCookies(cookieString: string): Record<string, string> {
155
+ return cookieString
156
+ .split("; ")
157
+ .reduce((acc, cookie) => {
158
+ const [key, value] = cookie.split("=");
159
+ if (key && value) {
160
+ acc[key] = value;
161
+ }
162
+ return acc;
163
+ }, {} as Record<string, string>);
164
+ }
165
+
166
+ /**
167
+ * Serialize cookie for Set-Cookie header
168
+ *
169
+ * Utility function for creating Set-Cookie header values in API routes.
170
+ *
171
+ * @param name - Cookie name
172
+ * @param value - Cookie value
173
+ * @param options - Cookie options (maxAge, path, etc.)
174
+ * @returns Serialized cookie string for Set-Cookie header
175
+ *
176
+ * @example
177
+ * ```tsx
178
+ * const cookieHeader = serializeCookie(
179
+ * AUTH_COOKIE_NAME,
180
+ * token,
181
+ * AUTH_COOKIE_CONFIG
182
+ * );
183
+ * response.headers.set('Set-Cookie', cookieHeader);
184
+ * ```
185
+ */
186
+ export function serializeCookie(
187
+ name: string,
188
+ value: string,
189
+ options: {
190
+ maxAge?: number;
191
+ path?: string;
192
+ sameSite?: "strict" | "lax" | "none";
193
+ secure?: boolean;
194
+ httpOnly?: boolean;
195
+ } = {}
196
+ ): string {
197
+ const parts = [`${name}=${value}`];
198
+
199
+ if (options.maxAge) {
200
+ parts.push(`Max-Age=${options.maxAge}`);
201
+ }
202
+
203
+ if (options.path) {
204
+ parts.push(`Path=${options.path}`);
205
+ }
206
+
207
+ if (options.sameSite) {
208
+ parts.push(`SameSite=${options.sameSite}`);
209
+ }
210
+
211
+ if (options.secure) {
212
+ parts.push("Secure");
213
+ }
214
+
215
+ if (options.httpOnly) {
216
+ parts.push("HttpOnly");
217
+ }
218
+
219
+ return parts.join("; ");
220
+ }