@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,310 @@
1
+ /**
2
+ * Authentication Hook
3
+ *
4
+ * Provides login, logout, and authentication state management.
5
+ *
6
+ * Security Model:
7
+ * 1. Login: Calls GraphQL mutation → Stores token in httpOnly cookie via API route
8
+ * 2. Logout: Calls GraphQL mutation → Clears httpOnly cookie via API route
9
+ * 3. Token validation: Happens on GraphQL backend (not client-side)
10
+ *
11
+ * Usage:
12
+ * - Login/Register pages: Call login() after successful mutation
13
+ * - Protected pages: Use AuthGuard component (checks cookie existence)
14
+ * - Logout button: Call logout() to clear session
15
+ *
16
+ * @see lib/auth/cookies.ts - Cookie helper functions
17
+ * @see lib/auth/routes.ts - Route configuration
18
+ * @see proxy.ts - Server-side route protection
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * function LoginForm() {
23
+ * const { login, isLoading, error } = useAuth();
24
+ *
25
+ * const handleSubmit = async (e) => {
26
+ * e.preventDefault();
27
+ * const result = await login(email, password);
28
+ * if (result.userErrors.length === 0) {
29
+ * // Success - redirected to /account
30
+ * }
31
+ * };
32
+ *
33
+ * return <form onSubmit={handleSubmit}>...</form>;
34
+ * }
35
+ * ```
36
+ */
37
+
38
+ "use client";
39
+
40
+ import { useState } from "react";
41
+ import { useRouter, useSearchParams } from "next/navigation";
42
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
43
+ import { getGraphQLClient } from "@/lib/graphql/client";
44
+ import {
45
+ CustomerLoginDocument,
46
+ CustomerLogoutDocument,
47
+ CustomerTokenRenewDocument,
48
+ type CustomerLoginMutation,
49
+ type CustomerLogoutMutation,
50
+ type CustomerTokenRenewMutation,
51
+ } from "@/generated/graphql";
52
+ import { setAuthToken, clearAuthToken, getAuthToken } from "@/lib/auth/cookies";
53
+ import { redirects } from "@/lib/auth/routes";
54
+
55
+ /**
56
+ * Login input
57
+ */
58
+ export interface LoginInput {
59
+ email: string;
60
+ password: string;
61
+ }
62
+
63
+ /**
64
+ * Login result
65
+ */
66
+ export interface LoginResult {
67
+ success: boolean;
68
+ userErrors: Array<{ message: string; field?: string[] }>;
69
+ accessToken?: string;
70
+ expiresAt?: string;
71
+ }
72
+
73
+ /**
74
+ * Logout result
75
+ */
76
+ export interface LogoutResult {
77
+ success: boolean;
78
+ userErrors: Array<{ message: string; field?: string[] }>;
79
+ }
80
+
81
+ /**
82
+ * Token renew result
83
+ */
84
+ export interface TokenRenewResult {
85
+ success: boolean;
86
+ userErrors: Array<{ message: string; field?: string[] }>;
87
+ accessToken?: string;
88
+ expiresAt?: string;
89
+ }
90
+
91
+ /**
92
+ * Authentication hook
93
+ */
94
+ export function useAuth() {
95
+ const router = useRouter();
96
+ const searchParams = useSearchParams();
97
+ const queryClient = useQueryClient();
98
+ const client = getGraphQLClient();
99
+
100
+ const [error, setError] = useState<string | null>(null);
101
+
102
+ /**
103
+ * Login mutation
104
+ */
105
+ const loginMutation = useMutation({
106
+ mutationFn: async (input: LoginInput): Promise<LoginResult> => {
107
+ const data = await client.request<CustomerLoginMutation>(
108
+ CustomerLoginDocument,
109
+ { input }
110
+ );
111
+
112
+ const { customerAccessToken, userErrors } =
113
+ data.customerAccessTokenCreate;
114
+
115
+ if (userErrors && userErrors.length > 0) {
116
+ return {
117
+ success: false,
118
+ userErrors: userErrors.map((e: any) => ({
119
+ message: e.message,
120
+ field: e.field || undefined,
121
+ })),
122
+ };
123
+ }
124
+
125
+ if (!customerAccessToken) {
126
+ return {
127
+ success: false,
128
+ userErrors: [{ message: "Failed to create access token" }],
129
+ };
130
+ }
131
+
132
+ // Store token in httpOnly cookie via API route
133
+ await setAuthToken(customerAccessToken.accessToken);
134
+
135
+ return {
136
+ success: true,
137
+ userErrors: [],
138
+ accessToken: customerAccessToken.accessToken,
139
+ expiresAt: customerAccessToken.expiresAt,
140
+ };
141
+ },
142
+ onSuccess: (result: LoginResult) => {
143
+ if (result.success) {
144
+ // Clear any cached queries that might need authentication
145
+ queryClient.invalidateQueries();
146
+
147
+ // Redirect to original destination or account page
148
+ const redirect = searchParams.get("redirect");
149
+ router.push(redirect || redirects.authenticated);
150
+ }
151
+ },
152
+ onError: (err: unknown) => {
153
+ setError(err instanceof Error ? err.message : "Login failed");
154
+ },
155
+ });
156
+
157
+ /**
158
+ * Logout mutation
159
+ */
160
+ const logoutMutation = useMutation({
161
+ mutationFn: async (): Promise<LogoutResult> => {
162
+ const token = getAuthToken();
163
+
164
+ if (!token) {
165
+ // No token to logout
166
+ return {
167
+ success: true,
168
+ userErrors: [],
169
+ };
170
+ }
171
+
172
+ const data = await client.request<CustomerLogoutMutation>(
173
+ CustomerLogoutDocument,
174
+ { customerAccessToken: token }
175
+ );
176
+
177
+ const { userErrors } = data.customerAccessTokenDelete;
178
+
179
+ if (userErrors && userErrors.length > 0) {
180
+ return {
181
+ success: false,
182
+ userErrors: userErrors.map((e: any) => ({
183
+ message: e.message,
184
+ field: e.field || undefined,
185
+ })),
186
+ };
187
+ }
188
+
189
+ // Clear httpOnly cookie via API route
190
+ await clearAuthToken();
191
+
192
+ return {
193
+ success: true,
194
+ userErrors: [],
195
+ };
196
+ },
197
+ onSuccess: (result: LogoutResult) => {
198
+ if (result.success) {
199
+ // Clear all cached queries
200
+ queryClient.clear();
201
+
202
+ // Redirect to home page
203
+ router.push("/");
204
+ }
205
+ },
206
+ onError: (err: unknown) => {
207
+ setError(err instanceof Error ? err.message : "Logout failed");
208
+ },
209
+ });
210
+
211
+ /**
212
+ * Token renew mutation
213
+ */
214
+ const renewTokenMutation = useMutation({
215
+ mutationFn: async (): Promise<TokenRenewResult> => {
216
+ const token = getAuthToken();
217
+
218
+ if (!token) {
219
+ return {
220
+ success: false,
221
+ userErrors: [{ message: "No token to renew" }],
222
+ };
223
+ }
224
+
225
+ const data = await client.request<CustomerTokenRenewMutation>(
226
+ CustomerTokenRenewDocument,
227
+ { customerAccessToken: token }
228
+ );
229
+
230
+ const { customerAccessToken, userErrors } =
231
+ data.customerAccessTokenRenew;
232
+
233
+ if (userErrors && userErrors.length > 0) {
234
+ return {
235
+ success: false,
236
+ userErrors: userErrors.map((e: any) => ({
237
+ message: e.message,
238
+ field: e.field || undefined,
239
+ })),
240
+ };
241
+ }
242
+
243
+ if (!customerAccessToken) {
244
+ return {
245
+ success: false,
246
+ userErrors: [{ message: "Failed to renew access token" }],
247
+ };
248
+ }
249
+
250
+ // Update token in httpOnly cookie via API route
251
+ await setAuthToken(customerAccessToken.accessToken);
252
+
253
+ return {
254
+ success: true,
255
+ userErrors: [],
256
+ accessToken: customerAccessToken.accessToken,
257
+ expiresAt: customerAccessToken.expiresAt,
258
+ };
259
+ },
260
+ onError: (err: unknown) => {
261
+ setError(err instanceof Error ? err.message : "Token renewal failed");
262
+ },
263
+ });
264
+
265
+ /**
266
+ * Login function
267
+ */
268
+ const login = async (email: string, password: string): Promise<LoginResult> => {
269
+ setError(null);
270
+ return loginMutation.mutateAsync({ email, password });
271
+ };
272
+
273
+ /**
274
+ * Logout function
275
+ */
276
+ const logout = async (): Promise<LogoutResult> => {
277
+ setError(null);
278
+ return logoutMutation.mutateAsync();
279
+ };
280
+
281
+ /**
282
+ * Renew token function
283
+ */
284
+ const renewToken = async (): Promise<TokenRenewResult> => {
285
+ setError(null);
286
+ return renewTokenMutation.mutateAsync();
287
+ };
288
+
289
+ return {
290
+ // Functions
291
+ login,
292
+ logout,
293
+ renewToken,
294
+
295
+ // State
296
+ isLoggingIn: loginMutation.isPending,
297
+ isLoggingOut: logoutMutation.isPending,
298
+ isRenewingToken: renewTokenMutation.isPending,
299
+ isLoading:
300
+ loginMutation.isPending ||
301
+ logoutMutation.isPending ||
302
+ renewTokenMutation.isPending,
303
+ error,
304
+
305
+ // Results
306
+ loginResult: loginMutation.data,
307
+ logoutResult: logoutMutation.data,
308
+ renewResult: renewTokenMutation.data,
309
+ };
310
+ }
@@ -0,0 +1,286 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useRef } from 'react';
4
+ import { useCartStore } from '@/stores/cart-store';
5
+ import { useCartCreate, useCartLinesAdd, useCartLinesUpdate, useCartLinesRemove } from '@/lib/graphql/hooks';
6
+ import { toast } from 'sonner';
7
+
8
+ // Debounce delay for quantity updates (prevents rate limiting)
9
+ const QUANTITY_UPDATE_DEBOUNCE_MS = 500;
10
+
11
+ /**
12
+ * Hook for cart mutations — server-only, no local state manipulation.
13
+ *
14
+ * All mutations go through GraphQL. React Query cache invalidation
15
+ * (configured in hooks.ts) automatically updates all consumers via useCartSync.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * const { addToCart, updateQuantity, removeFromCart } = useCartActions();
20
+ *
21
+ * await addToCart({
22
+ * variantId: 'variant-123',
23
+ * productId: 'product-456',
24
+ * productTitle: 'T-Shirt',
25
+ * variantTitle: 'Large / Blue',
26
+ * price: { amount: '29.99', currencyCode: 'USD' },
27
+ * quantity: 1
28
+ * });
29
+ *
30
+ * // updateQuantity and removeFromCart take lineId (not variantId)
31
+ * updateQuantity('line-abc', 3);
32
+ * removeFromCart('line-abc');
33
+ * ```
34
+ */
35
+ export function useCartActions() {
36
+ const {
37
+ setCartId,
38
+ clearCart,
39
+ openCart,
40
+ } = useCartStore();
41
+
42
+ // GraphQL mutations
43
+ const createCartMutation = useCartCreate();
44
+ const addLinesMutation = useCartLinesAdd();
45
+ const updateLinesMutation = useCartLinesUpdate();
46
+ const removeLinesMutation = useCartLinesRemove();
47
+
48
+ // Debounce refs for quantity updates (prevents ThrottlerException on rapid clicks)
49
+ const updateTimeoutRef = useRef<Map<string, NodeJS.Timeout>>(new Map());
50
+ const pendingUpdatesRef = useRef<Map<string, { lineId: string; quantity: number }>>(new Map());
51
+
52
+ /**
53
+ * Get or create cart ID.
54
+ * Reads cartId from store (fresh, not stale closure) or creates a new cart.
55
+ */
56
+ const getOrCreateCartId = useCallback(async (forceNew: boolean = false): Promise<string> => {
57
+ const currentCartId = useCartStore.getState().cartId;
58
+ if (currentCartId && !forceNew) {
59
+ return currentCartId;
60
+ }
61
+
62
+ try {
63
+ const result = await createCartMutation.mutateAsync({ input: {} });
64
+
65
+ if (result.cartCreate.cart) {
66
+ const newCartId = result.cartCreate.cart.id;
67
+ setCartId(newCartId);
68
+ return newCartId;
69
+ }
70
+
71
+ if (result.cartCreate.userErrors?.length > 0) {
72
+ throw new Error(result.cartCreate.userErrors[0].message);
73
+ }
74
+
75
+ throw new Error('Failed to create cart');
76
+ } catch (error: any) {
77
+ console.error('Cart creation failed:', error);
78
+ throw error;
79
+ }
80
+ }, [setCartId, createCartMutation]);
81
+
82
+ /**
83
+ * Check if error is a "Cart not found" error (stale/expired cart)
84
+ */
85
+ const isCartNotFoundError = (error: any): boolean => {
86
+ const message = error?.message || '';
87
+ return message.toLowerCase().includes('cart not found') ||
88
+ message.toLowerCase().includes('cart does not exist');
89
+ };
90
+
91
+ /**
92
+ * Add item to cart (server-only).
93
+ *
94
+ * Creates cart if needed. On "cart not found", clears cartId, creates new cart, retries once.
95
+ * React Query cache invalidation in hooks.ts updates all useCartSync consumers.
96
+ */
97
+ const addToCart = useCallback(async (item: {
98
+ variantId: string;
99
+ productId: string;
100
+ productHandle?: string;
101
+ productTitle: string;
102
+ variantTitle: string;
103
+ price: { amount: string; currencyCode: string };
104
+ image?: { url: string; altText?: string | null } | null;
105
+ available?: boolean;
106
+ quantity?: number;
107
+ }, _options?: { _forceNewCart?: boolean }) => {
108
+ const forceNewCart = _options?._forceNewCart ?? false;
109
+
110
+ try {
111
+ const cartId = await getOrCreateCartId(forceNewCart);
112
+
113
+ const result = await addLinesMutation.mutateAsync({
114
+ cartId,
115
+ lines: [{
116
+ merchandiseId: item.variantId,
117
+ quantity: item.quantity ?? 1,
118
+ }],
119
+ });
120
+
121
+ if (result.cartLinesAdd.userErrors?.length > 0) {
122
+ const errorMessage = result.cartLinesAdd.userErrors[0].message;
123
+
124
+ if (isCartNotFoundError({ message: errorMessage }) && !forceNewCart) {
125
+ console.warn('Cart expired, creating new cart and retrying...');
126
+ setCartId(null);
127
+ return addToCart(item, { _forceNewCart: true });
128
+ }
129
+
130
+ throw new Error(errorMessage);
131
+ }
132
+
133
+ // Open cart drawer to show the added item
134
+ openCart();
135
+ toast.success('Added to cart');
136
+ } catch (error: any) {
137
+ if (isCartNotFoundError(error) && !forceNewCart) {
138
+ console.warn('Cart expired (caught), creating new cart and retrying...');
139
+ setCartId(null);
140
+ return addToCart(item, { _forceNewCart: true });
141
+ }
142
+
143
+ console.error('Add to cart failed:', error);
144
+ toast.error(error.message || 'Failed to add to cart');
145
+ throw error;
146
+ }
147
+ }, [setCartId, openCart, getOrCreateCartId, addLinesMutation]);
148
+
149
+ /**
150
+ * Execute the actual GraphQL update for a pending quantity change
151
+ */
152
+ const executeQuantityUpdate = useCallback(async (lineId: string, quantity: number) => {
153
+ try {
154
+ const currentCartId = useCartStore.getState().cartId;
155
+ if (!currentCartId) {
156
+ throw new Error('No cart found');
157
+ }
158
+
159
+ const result = await updateLinesMutation.mutateAsync({
160
+ cartId: currentCartId,
161
+ lines: [{
162
+ id: lineId,
163
+ quantity,
164
+ }],
165
+ });
166
+
167
+ if (result.cartLinesUpdate.userErrors?.length > 0) {
168
+ const errorMessage = result.cartLinesUpdate.userErrors[0].message;
169
+
170
+ if (isCartNotFoundError({ message: errorMessage })) {
171
+ console.warn('Cart expired during update, clearing cart');
172
+ clearCart();
173
+ toast.error('Your cart has expired. Please add items again.');
174
+ return;
175
+ }
176
+
177
+ throw new Error(errorMessage);
178
+ }
179
+ } catch (error: any) {
180
+ if (isCartNotFoundError(error)) {
181
+ console.warn('Cart expired during update (caught), clearing cart');
182
+ clearCart();
183
+ toast.error('Your cart has expired. Please add items again.');
184
+ return;
185
+ }
186
+
187
+ console.error('Update quantity failed:', error);
188
+ toast.error(error.message || 'Failed to update quantity');
189
+ }
190
+ }, [updateLinesMutation, clearCart]);
191
+
192
+ /**
193
+ * Remove item from cart by line ID.
194
+ *
195
+ * Silently handles expired cart (just clears stale cartId).
196
+ */
197
+ const removeFromCart = useCallback(async (lineId: string) => {
198
+ try {
199
+ const currentCartId = useCartStore.getState().cartId;
200
+ if (!currentCartId) {
201
+ return;
202
+ }
203
+
204
+ const result = await removeLinesMutation.mutateAsync({
205
+ cartId: currentCartId,
206
+ lineIds: [lineId],
207
+ });
208
+
209
+ if (result.cartLinesRemove.userErrors?.length > 0) {
210
+ const errorMessage = result.cartLinesRemove.userErrors[0].message;
211
+
212
+ if (isCartNotFoundError({ message: errorMessage })) {
213
+ console.warn('Cart expired during remove, clearing stale cartId');
214
+ setCartId(null);
215
+ return;
216
+ }
217
+
218
+ throw new Error(errorMessage);
219
+ }
220
+
221
+ toast.success('Removed from cart');
222
+ } catch (error: any) {
223
+ if (isCartNotFoundError(error)) {
224
+ console.warn('Cart expired during remove (caught), clearing stale cartId');
225
+ setCartId(null);
226
+ return;
227
+ }
228
+
229
+ console.error('Remove from cart failed:', error);
230
+ toast.error(error.message || 'Failed to remove from cart');
231
+ throw error;
232
+ }
233
+ }, [setCartId, removeLinesMutation]);
234
+
235
+ /**
236
+ * Update item quantity (debounced, takes lineId).
237
+ *
238
+ * Debounces GraphQL API calls to prevent ThrottlerException on rapid clicks.
239
+ * If quantity <= 0, removes the item instead.
240
+ */
241
+ const updateQuantity = useCallback((lineId: string, quantity: number) => {
242
+ if (quantity <= 0) {
243
+ removeFromCart(lineId);
244
+ return;
245
+ }
246
+
247
+ // Cancel any pending update for this line
248
+ const existingTimeout = updateTimeoutRef.current.get(lineId);
249
+ if (existingTimeout) {
250
+ clearTimeout(existingTimeout);
251
+ }
252
+
253
+ // Store the pending update
254
+ pendingUpdatesRef.current.set(lineId, { lineId, quantity });
255
+
256
+ // Schedule debounced API call
257
+ const timeout = setTimeout(() => {
258
+ const pending = pendingUpdatesRef.current.get(lineId);
259
+ if (pending) {
260
+ pendingUpdatesRef.current.delete(lineId);
261
+ updateTimeoutRef.current.delete(lineId);
262
+ executeQuantityUpdate(pending.lineId, pending.quantity);
263
+ }
264
+ }, QUANTITY_UPDATE_DEBOUNCE_MS);
265
+
266
+ updateTimeoutRef.current.set(lineId, timeout);
267
+ }, [removeFromCart, executeQuantityUpdate]);
268
+
269
+ /**
270
+ * Clear entire cart.
271
+ * Clears cartId in zustand persist → useCartSync returns empty.
272
+ */
273
+ const clearEntireCart = useCallback(() => {
274
+ clearCart();
275
+ toast.success('Cart cleared');
276
+ }, [clearCart]);
277
+
278
+ return {
279
+ addToCart,
280
+ updateQuantity,
281
+ removeFromCart,
282
+ clearCart: clearEntireCart,
283
+ isLoading: createCartMutation.isPending || addLinesMutation.isPending ||
284
+ updateLinesMutation.isPending || removeLinesMutation.isPending,
285
+ };
286
+ }
@@ -0,0 +1,110 @@
1
+ "use client";
2
+
3
+ import { useEffect } from "react";
4
+ import { useCart } from "@/lib/graphql/hooks";
5
+ import { useCartStore } from "@/stores/cart-store";
6
+
7
+ /**
8
+ * Mapped cart item for display components.
9
+ * Server is the single source of truth — no client-side items[].
10
+ */
11
+ export interface CartItemData {
12
+ lineId: string;
13
+ variantId: string;
14
+ productId: string;
15
+ productHandle?: string;
16
+ productTitle: string;
17
+ variantTitle: string;
18
+ productType?: string;
19
+ quantity: number;
20
+ price: { amount: string; currencyCode: string };
21
+ image?: { url: string; altText?: string | null } | null;
22
+ available: boolean;
23
+ }
24
+
25
+ /**
26
+ * Primary data source for cart display.
27
+ *
28
+ * Reads cart from GraphQL (server as source of truth).
29
+ * Maps GraphQL lines to display-friendly CartItemData.
30
+ * Detects and auto-clears stale cart IDs.
31
+ */
32
+ export function useCartSync() {
33
+ const cartId = useCartStore((state) => state.cartId);
34
+ const isHydrated = useCartStore((state) => state.isHydrated);
35
+ const setCartId = useCartStore((state) => state.setCartId);
36
+
37
+ const { data, isLoading, error, refetch } = useCart(cartId, {
38
+ enabled: isHydrated && Boolean(cartId),
39
+ retry: false,
40
+ });
41
+
42
+ const cart = data?.cart;
43
+
44
+ // Detect stale cart: cartId exists but server returns nothing
45
+ const isStaleCart = isHydrated && Boolean(cartId) && !isLoading && !cart;
46
+
47
+ // Auto-clear stale cartId
48
+ useEffect(() => {
49
+ if (isStaleCart) {
50
+ setCartId(null);
51
+ }
52
+ }, [isStaleCart, setCartId]);
53
+
54
+ // Map GraphQL lines to display-friendly items
55
+ const items: CartItemData[] = (cart?.lines ?? []).map((line: any) => {
56
+ const merchandiseTitle = line.merchandise.title ?? "";
57
+ // Hide generic variant names like "Default" or "Default Title"
58
+ const isDefaultVariant = /^default(\s+title)?$/i.test(merchandiseTitle);
59
+ const productTitle = line.productTitle || merchandiseTitle;
60
+ const variantTitle = isDefaultVariant ? "" : merchandiseTitle;
61
+
62
+ return {
63
+ lineId: line.id,
64
+ variantId: line.merchandise.id,
65
+ productId: line.productId || line.merchandise.id,
66
+ productHandle: line.productHandle,
67
+ productTitle,
68
+ variantTitle,
69
+ productType: line.productType,
70
+ quantity: line.quantity,
71
+ price: {
72
+ amount: line.merchandise.price.amount,
73
+ currencyCode: line.merchandise.price.currencyCode,
74
+ },
75
+ image: line.merchandise.image || null,
76
+ available: line.merchandise.available,
77
+ };
78
+ });
79
+
80
+ const totalQuantity = cart?.totalQuantity ?? 0;
81
+ const subtotal = cart?.cost?.subtotalAmount
82
+ ? parseFloat(cart.cost.subtotalAmount.amount)
83
+ : 0;
84
+ const total = cart?.cost?.totalAmount
85
+ ? parseFloat(cart.cost.totalAmount.amount)
86
+ : subtotal;
87
+ const currency = cart?.cost?.subtotalAmount?.currencyCode ?? "PLN";
88
+
89
+ // Discount data from server
90
+ const discountCodes: string[] = (cart?.discountCodes ?? [])
91
+ .filter((dc: any) => dc.applicable)
92
+ .map((dc: any) => dc.code);
93
+ const totalDiscount = subtotal - total;
94
+
95
+ return {
96
+ cart,
97
+ cartId,
98
+ items,
99
+ totalQuantity,
100
+ subtotal,
101
+ total,
102
+ currency,
103
+ discountCodes,
104
+ totalDiscount,
105
+ isLoading: !isHydrated || isLoading,
106
+ isStaleCart,
107
+ error,
108
+ refetch,
109
+ };
110
+ }