@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,94 @@
1
+ "use client";
2
+
3
+ import { useEffect, ReactNode } from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { useAuth } from "@doswiftly/commerce-sdk/graphql/react";
6
+ import { redirects } from "@/lib/auth/routes";
7
+
8
+ interface AuthGuardProps {
9
+ children: ReactNode;
10
+ /** URL to redirect to (default: from lib/auth/routes.ts) */
11
+ redirectTo?: string;
12
+ /** Custom loading component */
13
+ fallback?: ReactNode;
14
+ /** If true, only guests can access (redirects authenticated users) */
15
+ requireGuest?: boolean;
16
+ }
17
+
18
+ /**
19
+ * AuthGuard - Client-side route protection based on authentication status
20
+ *
21
+ * Security Model (Layer 2 of 3):
22
+ * 1. Middleware - Fast redirect based on cookie (runs first)
23
+ * 2. AuthGuard (this) - Client-side validation via useAuth hook
24
+ * 3. GraphQL Backend - Ultimate security (validates token)
25
+ *
26
+ * Why use AuthGuard if middleware exists?
27
+ * - Middleware only checks cookie existence (fast but not validation)
28
+ * - AuthGuard validates token via useAuth (catches expired/invalid tokens)
29
+ * - Provides loading states during redirect
30
+ * - Works for client-side navigation (middleware only runs on initial load)
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * // Protected route (requires login)
35
+ * export default function AccountPage() {
36
+ * return (
37
+ * <AuthGuard>
38
+ * <AccountContent />
39
+ * </AuthGuard>
40
+ * );
41
+ * }
42
+ *
43
+ * // Guest-only route (login/register pages)
44
+ * export default function LoginPage() {
45
+ * return (
46
+ * <AuthGuard requireGuest>
47
+ * <LoginForm />
48
+ * </AuthGuard>
49
+ * );
50
+ * }
51
+ *
52
+ * // Custom redirect (e.g., preserve checkout URL)
53
+ * export default function CheckoutPage() {
54
+ * return (
55
+ * <AuthGuard redirectTo="/auth/login?redirect=/checkout">
56
+ * <CheckoutContent />
57
+ * </AuthGuard>
58
+ * );
59
+ * }
60
+ * ```
61
+ *
62
+ * @see lib/auth/routes.ts - SSOT for route configuration
63
+ * @see middleware.ts - Server-side route protection
64
+ */
65
+ export function AuthGuard({
66
+ children,
67
+ redirectTo,
68
+ fallback,
69
+ requireGuest = false,
70
+ }: AuthGuardProps) {
71
+ const router = useRouter();
72
+ const { isAuthenticated } = useAuth();
73
+
74
+ // Use SSOT defaults from routes.ts
75
+ const defaultRedirect = requireGuest
76
+ ? redirects.authenticated
77
+ : redirects.unauthenticated;
78
+ const targetRedirect = redirectTo ?? defaultRedirect;
79
+
80
+ // Determine if we should redirect
81
+ const shouldRedirect = requireGuest ? isAuthenticated : !isAuthenticated;
82
+
83
+ useEffect(() => {
84
+ if (shouldRedirect) {
85
+ // Use replace instead of push to prevent back button issues
86
+ router.replace(targetRedirect);
87
+ }
88
+ }, [shouldRedirect, targetRedirect, router]);
89
+
90
+ // Always render children immediately (non-blocking)
91
+ // Redirect happens async in useEffect
92
+ // This prevents blocking router.replace() execution
93
+ return <>{children}</>;
94
+ }
@@ -0,0 +1,77 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { ShoppingCart, Loader2, Check } from "lucide-react";
5
+ import { useCartManager } from "@doswiftly/commerce-sdk/graphql/react";
6
+
7
+ interface AddToCartButtonProps {
8
+ variantId: string;
9
+ quantity?: number;
10
+ disabled?: boolean;
11
+ }
12
+
13
+ /**
14
+ * AddToCartButton - Developer-friendly add to cart button
15
+ *
16
+ * Uses useCartManager hook - all loading/error states come from the hook.
17
+ * No manual state management needed!
18
+ *
19
+ * @example
20
+ * ```tsx
21
+ * <AddToCartButton variantId="variant-123" />
22
+ * <AddToCartButton variantId="variant-123" quantity={2} />
23
+ * <AddToCartButton variantId="variant-123" disabled={!inStock} />
24
+ * ```
25
+ */
26
+ export function AddToCartButton({
27
+ variantId,
28
+ quantity = 1,
29
+ disabled = false,
30
+ }: AddToCartButtonProps) {
31
+ // Success state (only local state needed - for brief "Added!" feedback)
32
+ const [justAdded, setJustAdded] = useState(false);
33
+
34
+ // All cart logic from hook - no manual state management!
35
+ const { addItem, addItemLoading } = useCartManager();
36
+
37
+ const handleAddToCart = async () => {
38
+ if (disabled || addItemLoading) return;
39
+
40
+ try {
41
+ await addItem(variantId, quantity);
42
+ setJustAdded(true);
43
+ setTimeout(() => setJustAdded(false), 2000);
44
+ } catch (err) {
45
+ console.error("Failed to add to cart:", err);
46
+ }
47
+ };
48
+
49
+ return (
50
+ <button
51
+ onClick={handleAddToCart}
52
+ disabled={disabled || addItemLoading}
53
+ className={`btn w-full ${
54
+ justAdded
55
+ ? "bg-green-500 text-white hover:bg-green-600"
56
+ : "bg-gray-900 text-white hover:bg-gray-800"
57
+ }`}
58
+ >
59
+ {addItemLoading ? (
60
+ <>
61
+ <Loader2 className="mr-2 h-5 w-5 animate-spin" />
62
+ Adding...
63
+ </>
64
+ ) : justAdded ? (
65
+ <>
66
+ <Check className="mr-2 h-5 w-5" />
67
+ Added to Cart!
68
+ </>
69
+ ) : (
70
+ <>
71
+ <ShoppingCart className="mr-2 h-5 w-5" />
72
+ Add to Cart
73
+ </>
74
+ )}
75
+ </button>
76
+ );
77
+ }
@@ -0,0 +1,29 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { ShoppingCart } from "lucide-react";
5
+ import { useCartManager } from "@doswiftly/commerce-sdk/graphql/react";
6
+
7
+ /**
8
+ * CartIcon - Cart icon with dynamic item count
9
+ *
10
+ * Shows total quantity from useCartManager.
11
+ * Updates automatically when cart changes.
12
+ */
13
+ export function CartIcon() {
14
+ const { totalQuantity } = useCartManager();
15
+
16
+ return (
17
+ <Link
18
+ href="/cart"
19
+ className="relative p-2 text-gray-600 hover:text-primary"
20
+ >
21
+ <ShoppingCart className="h-5 w-5" />
22
+ {totalQuantity > 0 && (
23
+ <span className="absolute -right-1 -top-1 flex h-5 w-5 items-center justify-center rounded-full bg-primary text-xs text-white">
24
+ {totalQuantity > 99 ? "99+" : totalQuantity}
25
+ </span>
26
+ )}
27
+ </Link>
28
+ );
29
+ }
@@ -0,0 +1,217 @@
1
+ "use client";
2
+
3
+ import { useState, useRef, useEffect } from "react";
4
+ import { ChevronDown, Check, Globe } from "lucide-react";
5
+ import { useCurrency } from "@doswiftly/commerce-sdk/graphql/react";
6
+ import {
7
+ useCurrencyStore,
8
+ selectIsHydrated,
9
+ } from "@doswiftly/commerce-sdk/graphql/react/stores";
10
+
11
+ // ============================================================================
12
+ // CURRENCY DATA
13
+ // ============================================================================
14
+
15
+ const CURRENCY_SYMBOLS: Record<string, string> = {
16
+ PLN: "zł",
17
+ EUR: "€",
18
+ USD: "$",
19
+ GBP: "£",
20
+ CHF: "CHF",
21
+ CZK: "Kč",
22
+ SEK: "kr",
23
+ NOK: "kr",
24
+ DKK: "kr",
25
+ JPY: "¥",
26
+ CNY: "¥",
27
+ AUD: "A$",
28
+ CAD: "C$",
29
+ };
30
+
31
+ const CURRENCY_NAMES: Record<string, string> = {
32
+ PLN: "Polski złoty",
33
+ EUR: "Euro",
34
+ USD: "US Dollar",
35
+ GBP: "British Pound",
36
+ CHF: "Swiss Franc",
37
+ CZK: "Czech Koruna",
38
+ SEK: "Swedish Krona",
39
+ NOK: "Norwegian Krone",
40
+ DKK: "Danish Krone",
41
+ JPY: "Japanese Yen",
42
+ CNY: "Chinese Yuan",
43
+ AUD: "Australian Dollar",
44
+ CAD: "Canadian Dollar",
45
+ };
46
+
47
+ // ============================================================================
48
+ // TYPES
49
+ // ============================================================================
50
+
51
+ interface CurrencySelectorProps {
52
+ className?: string;
53
+ showSymbol?: boolean;
54
+ showIcon?: boolean;
55
+ variant?: "default" | "compact" | "minimal";
56
+ }
57
+
58
+ // ============================================================================
59
+ // COMPONENT
60
+ // ============================================================================
61
+
62
+ export function CurrencySelector({
63
+ className = "",
64
+ showSymbol = false,
65
+ showIcon = true,
66
+ variant = "default",
67
+ }: CurrencySelectorProps) {
68
+ // useCurrency from SDK context - setCurrency includes QueryClient invalidation
69
+ const { currency, supportedCurrencies, setCurrency, isAutoDetected } = useCurrency();
70
+
71
+ // Hydration state from Zustand (prevents SSR mismatch flash)
72
+ const isHydrated = useCurrencyStore(selectIsHydrated);
73
+
74
+ const [isOpen, setIsOpen] = useState(false);
75
+ const dropdownRef = useRef<HTMLDivElement>(null);
76
+
77
+ // Close dropdown on outside click
78
+ useEffect(() => {
79
+ function handleClickOutside(event: MouseEvent) {
80
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
81
+ setIsOpen(false);
82
+ }
83
+ }
84
+ document.addEventListener("mousedown", handleClickOutside);
85
+ return () => document.removeEventListener("mousedown", handleClickOutside);
86
+ }, []);
87
+
88
+ // Close on escape
89
+ useEffect(() => {
90
+ function handleEscape(event: KeyboardEvent) {
91
+ if (event.key === "Escape") setIsOpen(false);
92
+ }
93
+ document.addEventListener("keydown", handleEscape);
94
+ return () => document.removeEventListener("keydown", handleEscape);
95
+ }, []);
96
+
97
+ // Prevent SSR mismatch - skeleton until hydrated
98
+ if (!isHydrated) {
99
+ return (
100
+ <div className={`inline-block ${className}`}>
101
+ <div className="px-3 py-2 bg-gray-100 rounded-lg animate-pulse w-16 h-9" />
102
+ </div>
103
+ );
104
+ }
105
+
106
+ const handleSelect = (newCurrency: string) => {
107
+ if (newCurrency !== currency) {
108
+ setCurrency(newCurrency); // Includes QueryClient invalidation
109
+ }
110
+ setIsOpen(false);
111
+ };
112
+
113
+ const displayValue = showSymbol ? (CURRENCY_SYMBOLS[currency] || currency) : currency;
114
+
115
+ const buttonStyles = {
116
+ default: "px-3 py-2 text-sm",
117
+ compact: "px-2 py-1.5 text-xs",
118
+ minimal: "px-2 py-1 text-xs",
119
+ };
120
+
121
+ const dropdownStyles = {
122
+ default: "w-48",
123
+ compact: "w-40",
124
+ minimal: "w-36",
125
+ };
126
+
127
+ return (
128
+ <div ref={dropdownRef} className={`relative inline-block ${className}`}>
129
+ <button
130
+ type="button"
131
+ onClick={() => setIsOpen(!isOpen)}
132
+ className={`
133
+ inline-flex items-center justify-between gap-2
134
+ bg-white dark:bg-gray-800
135
+ border border-gray-200 dark:border-gray-700
136
+ rounded-lg
137
+ text-gray-700 dark:text-gray-200
138
+ hover:bg-gray-50 dark:hover:bg-gray-700
139
+ focus:outline-none focus:ring-2 focus:ring-blue-500
140
+ transition-colors
141
+ ${buttonStyles[variant]}
142
+ `}
143
+ aria-expanded={isOpen}
144
+ aria-haspopup="listbox"
145
+ >
146
+ {showIcon && variant !== "minimal" && (
147
+ <Globe className="w-4 h-4 text-gray-400" />
148
+ )}
149
+ <span className="font-medium">{displayValue}</span>
150
+ {isAutoDetected && variant === "default" && (
151
+ <span className="text-xs text-gray-400">(auto)</span>
152
+ )}
153
+ <ChevronDown
154
+ className={`w-4 h-4 text-gray-400 transition-transform ${isOpen ? "rotate-180" : ""}`}
155
+ />
156
+ </button>
157
+
158
+ {isOpen && (
159
+ <div
160
+ className={`
161
+ absolute right-0 mt-2 z-50
162
+ bg-white dark:bg-gray-800
163
+ border border-gray-200 dark:border-gray-700
164
+ rounded-lg shadow-lg
165
+ overflow-hidden
166
+ ${dropdownStyles[variant]}
167
+ `}
168
+ role="listbox"
169
+ >
170
+ <div className="py-1 max-h-64 overflow-y-auto">
171
+ {supportedCurrencies.map((code) => {
172
+ const isSelected = code === currency;
173
+ const symbol = CURRENCY_SYMBOLS[code] || code;
174
+ const name = CURRENCY_NAMES[code] || code;
175
+
176
+ return (
177
+ <button
178
+ key={code}
179
+ type="button"
180
+ onClick={() => handleSelect(code)}
181
+ className={`
182
+ w-full px-3 py-2 text-left
183
+ flex items-center justify-between gap-2
184
+ hover:bg-gray-100 dark:hover:bg-gray-700
185
+ transition-colors
186
+ ${isSelected ? "bg-blue-50 dark:bg-blue-900/20" : ""}
187
+ `}
188
+ role="option"
189
+ aria-selected={isSelected}
190
+ >
191
+ <div className="flex items-center gap-2">
192
+ <span className="w-6 text-center text-gray-400 font-mono text-sm">
193
+ {symbol}
194
+ </span>
195
+ <div>
196
+ <span className="font-medium text-gray-900 dark:text-gray-100">
197
+ {code}
198
+ </span>
199
+ {variant === "default" && (
200
+ <span className="ml-2 text-xs text-gray-500 dark:text-gray-400">
201
+ {name}
202
+ </span>
203
+ )}
204
+ </div>
205
+ </div>
206
+ {isSelected && <Check className="w-4 h-4 text-blue-500" />}
207
+ </button>
208
+ );
209
+ })}
210
+ </div>
211
+ </div>
212
+ )}
213
+ </div>
214
+ );
215
+ }
216
+
217
+ export default CurrencySelector;
@@ -0,0 +1,62 @@
1
+ "use client";
2
+
3
+ import { useSearchParams, usePathname } from "next/navigation";
4
+ import Link from "next/link";
5
+
6
+ interface PaginationProps {
7
+ hasMore: boolean;
8
+ endCursor?: string | null;
9
+ currentCursor?: string;
10
+ totalShown: number;
11
+ }
12
+
13
+ /**
14
+ * Pagination - URL-based pagination that preserves filters
15
+ */
16
+ export function Pagination({
17
+ hasMore,
18
+ endCursor,
19
+ currentCursor,
20
+ totalShown,
21
+ }: PaginationProps) {
22
+ const pathname = usePathname();
23
+ const searchParams = useSearchParams();
24
+
25
+ // Build URL preserving current filters
26
+ const buildUrl = (cursor?: string | null) => {
27
+ const params = new URLSearchParams(searchParams.toString());
28
+
29
+ if (cursor) {
30
+ params.set("after", cursor);
31
+ } else {
32
+ params.delete("after");
33
+ }
34
+
35
+ const queryString = params.toString();
36
+ return queryString ? `${pathname}?${queryString}` : pathname;
37
+ };
38
+
39
+ return (
40
+ <div className="mt-12 flex justify-center gap-2">
41
+ <Link
42
+ href={buildUrl()}
43
+ className={`btn btn-outline ${
44
+ !currentCursor ? "pointer-events-none opacity-50" : ""
45
+ }`}
46
+ >
47
+ First Page
48
+ </Link>
49
+ <span className="flex items-center px-4 text-gray-600">
50
+ Showing {totalShown} products
51
+ </span>
52
+ <Link
53
+ href={hasMore && endCursor ? buildUrl(endCursor) : "#"}
54
+ className={`btn btn-outline ${
55
+ !hasMore ? "pointer-events-none opacity-50" : ""
56
+ }`}
57
+ >
58
+ Next
59
+ </Link>
60
+ </div>
61
+ );
62
+ }
@@ -0,0 +1,135 @@
1
+ "use client";
2
+
3
+ import { useState, useMemo } from "react";
4
+ import { VariantSelector } from "@/components/commerce/variant-selector";
5
+ import { AddToCartButton } from "@/components/commerce/add-to-cart-button";
6
+ import { formatPrice } from "@/lib/currency";
7
+
8
+ interface ProductVariant {
9
+ id: string;
10
+ title: string;
11
+ available: boolean;
12
+ selectedOptions: Array<{ name: string; value: string }>;
13
+ price: {
14
+ amount: string;
15
+ currencyCode: string;
16
+ };
17
+ compareAtPrice?: {
18
+ amount: string;
19
+ currencyCode: string;
20
+ } | null;
21
+ }
22
+
23
+ interface ProductActionsProps {
24
+ variants: ProductVariant[];
25
+ productId: string;
26
+ }
27
+
28
+ /**
29
+ * ProductActions - Client-side product interactions
30
+ *
31
+ * Handles variant selection, quantity, and add to cart.
32
+ * Server Component renders the rest of the page.
33
+ */
34
+ export function ProductActions({ variants, productId }: ProductActionsProps) {
35
+ // Store only variant ID - derive variant object from props (SSOT)
36
+ const [selectedVariantId, setSelectedVariantId] = useState<string | null>(
37
+ () => variants[0]?.id ?? null
38
+ );
39
+ const [quantity, setQuantity] = useState(1);
40
+
41
+ // Derived state: automatically updates when variants prop changes (e.g., after currency change)
42
+ const selectedVariant = useMemo(() => {
43
+ if (!variants || variants.length === 0) return null;
44
+ const found = variants.find((v) => v.id === selectedVariantId);
45
+ // Fallback to first variant if selected ID not found in new variants
46
+ return found ?? variants[0];
47
+ }, [variants, selectedVariantId]);
48
+
49
+ // Handler for VariantSelector - extracts ID from variant object
50
+ const handleVariantChange = (variant: ProductVariant) => {
51
+ setSelectedVariantId(variant.id);
52
+ };
53
+
54
+ const inStock = selectedVariant?.available ?? true;
55
+ const price = selectedVariant?.price;
56
+ const compareAtPrice = selectedVariant?.compareAtPrice;
57
+
58
+ return (
59
+ <div className="space-y-6">
60
+ {/* Price */}
61
+ <div className="flex items-baseline gap-4">
62
+ <span className="text-2xl font-bold text-primary">
63
+ {formatPrice(price)}
64
+ </span>
65
+ {compareAtPrice && (
66
+ <span className="text-lg text-gray-400 line-through">
67
+ {formatPrice(compareAtPrice)}
68
+ </span>
69
+ )}
70
+ </div>
71
+
72
+ {/* Variant Selector */}
73
+ {variants.length > 1 && (
74
+ <VariantSelector
75
+ variants={variants}
76
+ selectedVariantId={selectedVariant?.id}
77
+ onVariantChange={handleVariantChange}
78
+ />
79
+ )}
80
+
81
+ {/* Stock Status */}
82
+ <div>
83
+ {inStock ? (
84
+ <span className="inline-flex items-center gap-2 text-green-600">
85
+ <span className="h-2 w-2 rounded-full bg-green-600" />
86
+ In Stock
87
+ </span>
88
+ ) : (
89
+ <span className="inline-flex items-center gap-2 text-red-600">
90
+ <span className="h-2 w-2 rounded-full bg-red-600" />
91
+ Out of Stock
92
+ </span>
93
+ )}
94
+ </div>
95
+
96
+ {/* Quantity & Add to Cart */}
97
+ <div className="space-y-4">
98
+ <div className="flex items-center gap-4">
99
+ <label className="text-sm font-medium text-gray-700">Quantity:</label>
100
+ <div className="flex items-center rounded-lg border border-gray-300">
101
+ <button
102
+ type="button"
103
+ onClick={() => setQuantity((q) => Math.max(1, q - 1))}
104
+ className="px-3 py-2 text-gray-600 hover:text-primary"
105
+ >
106
+ -
107
+ </button>
108
+ <input
109
+ type="number"
110
+ min="1"
111
+ value={quantity}
112
+ onChange={(e) =>
113
+ setQuantity(Math.max(1, parseInt(e.target.value) || 1))
114
+ }
115
+ className="w-16 border-x border-gray-300 py-2 text-center"
116
+ />
117
+ <button
118
+ type="button"
119
+ onClick={() => setQuantity((q) => q + 1)}
120
+ className="px-3 py-2 text-gray-600 hover:text-primary"
121
+ >
122
+ +
123
+ </button>
124
+ </div>
125
+ </div>
126
+
127
+ <AddToCartButton
128
+ variantId={selectedVariant?.id || productId}
129
+ quantity={quantity}
130
+ disabled={!inStock}
131
+ />
132
+ </div>
133
+ </div>
134
+ );
135
+ }