@doswiftly/cli 0.1.17 → 0.1.19

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 (213) hide show
  1. package/README.md +23 -323
  2. package/dist/commands/check.js +1 -1
  3. package/dist/commands/check.js.map +1 -1
  4. package/dist/commands/deploy.d.ts.map +1 -1
  5. package/dist/commands/deploy.js +43 -20
  6. package/dist/commands/deploy.js.map +1 -1
  7. package/dist/commands/doctor.js +3 -3
  8. package/dist/commands/doctor.js.map +1 -1
  9. package/dist/commands/init.js +4 -4
  10. package/dist/commands/sdk.js +5 -5
  11. package/dist/commands/sdk.js.map +1 -1
  12. package/dist/commands/template.js +4 -4
  13. package/dist/commands/template.js.map +1 -1
  14. package/dist/commands/types.js +5 -5
  15. package/dist/commands/types.js.map +1 -1
  16. package/dist/commands/verify.js +2 -2
  17. package/dist/commands/verify.js.map +1 -1
  18. package/dist/lib/package-manager.d.ts +1 -1
  19. package/dist/lib/package-manager.js +1 -1
  20. package/package.json +1 -1
  21. package/templates/storefront-minimal/wrangler.toml +4 -0
  22. package/templates/storefront-nextjs/README.md +16 -12
  23. package/templates/storefront-nextjs/app/account/orders/page.tsx +2 -2
  24. package/templates/storefront-nextjs/app/account/page.tsx +2 -2
  25. package/templates/storefront-nextjs/app/auth/login/page.tsx +1 -1
  26. package/templates/storefront-nextjs/app/auth/register/page.tsx +1 -1
  27. package/templates/storefront-nextjs/app/cart/page.tsx +1 -1
  28. package/templates/storefront-nextjs/app/categories/[slug]/page.tsx +2 -2
  29. package/templates/storefront-nextjs/app/categories/page.tsx +1 -1
  30. package/templates/storefront-nextjs/app/collections/[slug]/page.tsx +1 -1
  31. package/templates/storefront-nextjs/app/collections/page.tsx +1 -1
  32. package/templates/storefront-nextjs/app/page.tsx +1 -1
  33. package/templates/storefront-nextjs/app/products/[slug]/page.tsx +1 -1
  34. package/templates/storefront-nextjs/app/products/page.tsx +2 -2
  35. package/templates/storefront-nextjs/app/search/page.tsx +1 -1
  36. package/templates/storefront-nextjs/components/auth/auth-guard.tsx +1 -1
  37. package/templates/storefront-nextjs/components/commerce/add-to-cart-button.tsx +1 -1
  38. package/templates/storefront-nextjs/components/commerce/cart-icon.tsx +1 -1
  39. package/templates/storefront-nextjs/components/commerce/currency-selector.tsx +2 -2
  40. package/templates/storefront-nextjs/components/commerce/product-filters.tsx +1 -1
  41. package/templates/storefront-nextjs/components/commerce/product-price.tsx +1 -1
  42. package/templates/storefront-nextjs/components/commerce/search-input.tsx +1 -1
  43. package/templates/storefront-nextjs/components/commerce/sort-select.tsx +1 -1
  44. package/templates/storefront-nextjs/components/providers.tsx +1 -1
  45. package/templates/storefront-nextjs/lib/currency.tsx +3 -3
  46. package/templates/storefront-nextjs/lib/format.ts +1 -1
  47. package/templates/storefront-nextjs/lib/graphql-queries.ts +3 -3
  48. package/templates/storefront-nextjs/package.dev.json +1 -1
  49. package/templates/storefront-nextjs/package.json +1 -1
  50. package/templates/storefront-nextjs/package.json.template +1 -1
  51. package/templates/storefront-nextjs/wrangler.toml +4 -0
  52. package/templates/storefront-nextjs-shadcn/.github/workflows/deploy.yml +47 -0
  53. package/templates/storefront-nextjs-shadcn/.github/workflows/preview.yml +47 -0
  54. package/templates/storefront-nextjs-shadcn/CLAUDE.md +148 -35
  55. package/templates/storefront-nextjs-shadcn/README.md +29 -162
  56. package/templates/storefront-nextjs-shadcn/app/account/addresses/page.tsx +98 -91
  57. package/templates/storefront-nextjs-shadcn/app/account/error.tsx +43 -0
  58. package/templates/storefront-nextjs-shadcn/app/account/loading.tsx +19 -0
  59. package/templates/storefront-nextjs-shadcn/app/account/loyalty/page.tsx +53 -162
  60. package/templates/storefront-nextjs-shadcn/app/account/orders/[id]/loading.tsx +60 -0
  61. package/templates/storefront-nextjs-shadcn/app/account/orders/[id]/page.tsx +36 -47
  62. package/templates/storefront-nextjs-shadcn/app/account/orders/page.tsx +46 -29
  63. package/templates/storefront-nextjs-shadcn/app/account/page.tsx +8 -5
  64. package/templates/storefront-nextjs-shadcn/app/account/settings/page.tsx +108 -71
  65. package/templates/storefront-nextjs-shadcn/app/api/auth/clear-token/route.ts +2 -86
  66. package/templates/storefront-nextjs-shadcn/app/api/auth/set-token/route.ts +2 -124
  67. package/templates/storefront-nextjs-shadcn/app/auth/forgot-password/page.tsx +10 -5
  68. package/templates/storefront-nextjs-shadcn/app/blog/[slug]/loading.tsx +17 -0
  69. package/templates/storefront-nextjs-shadcn/app/blog/[slug]/page.tsx +43 -2
  70. package/templates/storefront-nextjs-shadcn/app/blog/loading.tsx +19 -0
  71. package/templates/storefront-nextjs-shadcn/app/brands/page.tsx +2 -1
  72. package/templates/storefront-nextjs-shadcn/app/cart/loading.tsx +26 -0
  73. package/templates/storefront-nextjs-shadcn/app/cart/page.tsx +6 -3
  74. package/templates/storefront-nextjs-shadcn/app/categories/[slug]/category-products-client.tsx +56 -0
  75. package/templates/storefront-nextjs-shadcn/app/categories/[slug]/loading.tsx +32 -0
  76. package/templates/storefront-nextjs-shadcn/app/categories/[slug]/page.tsx +76 -59
  77. package/templates/storefront-nextjs-shadcn/app/categories/page.tsx +8 -4
  78. package/templates/storefront-nextjs-shadcn/app/checkout/error.tsx +43 -0
  79. package/templates/storefront-nextjs-shadcn/app/checkout/loading.tsx +31 -0
  80. package/templates/storefront-nextjs-shadcn/app/checkout/page.tsx +116 -79
  81. package/templates/storefront-nextjs-shadcn/app/collections/[handle]/loading.tsx +19 -0
  82. package/templates/storefront-nextjs-shadcn/app/collections/[handle]/page.tsx +1 -1
  83. package/templates/storefront-nextjs-shadcn/app/collections/loading.tsx +18 -0
  84. package/templates/storefront-nextjs-shadcn/app/collections/page.tsx +7 -4
  85. package/templates/storefront-nextjs-shadcn/app/global-error.tsx +117 -0
  86. package/templates/storefront-nextjs-shadcn/app/globals.css +8 -0
  87. package/templates/storefront-nextjs-shadcn/app/layout.tsx +46 -11
  88. package/templates/storefront-nextjs-shadcn/app/products/[slug]/error.tsx +43 -0
  89. package/templates/storefront-nextjs-shadcn/app/products/[slug]/loading.tsx +29 -0
  90. package/templates/storefront-nextjs-shadcn/app/products/[slug]/page.tsx +6 -6
  91. package/templates/storefront-nextjs-shadcn/app/products/[slug]/product-client.tsx +15 -61
  92. package/templates/storefront-nextjs-shadcn/app/products/loading.tsx +32 -0
  93. package/templates/storefront-nextjs-shadcn/app/products/products-client.tsx +405 -151
  94. package/templates/storefront-nextjs-shadcn/app/search/loading.tsx +18 -0
  95. package/templates/storefront-nextjs-shadcn/app/wishlist/page.tsx +8 -5
  96. package/templates/storefront-nextjs-shadcn/codegen.ts +48 -31
  97. package/templates/storefront-nextjs-shadcn/components/account/customer-info.fragment.graphql +36 -0
  98. package/templates/storefront-nextjs-shadcn/components/account/order-details.tsx +3 -1
  99. package/templates/storefront-nextjs-shadcn/components/account/order-history.tsx +26 -24
  100. package/templates/storefront-nextjs-shadcn/components/account/order-summary.fragment.graphql +36 -0
  101. package/templates/storefront-nextjs-shadcn/components/auth/account-menu.tsx +9 -9
  102. package/templates/storefront-nextjs-shadcn/components/auth/login-form.tsx +11 -37
  103. package/templates/storefront-nextjs-shadcn/components/auth/register-form.tsx +37 -23
  104. package/templates/storefront-nextjs-shadcn/components/cart/cart-drawer.tsx +4 -3
  105. package/templates/storefront-nextjs-shadcn/components/cart/cart-icon.tsx +8 -5
  106. package/templates/storefront-nextjs-shadcn/components/cart/cart-item.tsx +1 -1
  107. package/templates/storefront-nextjs-shadcn/components/cart/cart-line.fragment.graphql +53 -0
  108. package/templates/storefront-nextjs-shadcn/components/cart/cart-summary.tsx +1 -1
  109. package/templates/storefront-nextjs-shadcn/components/cart/shipping-estimator.tsx +22 -7
  110. package/templates/storefront-nextjs-shadcn/components/commerce/currency-selector.tsx +2 -2
  111. package/templates/storefront-nextjs-shadcn/components/commerce/product-actions.tsx +1 -1
  112. package/templates/storefront-nextjs-shadcn/components/commerce/search-input.tsx +2 -2
  113. package/templates/storefront-nextjs-shadcn/components/common/price-display.tsx +35 -11
  114. package/templates/storefront-nextjs-shadcn/components/discount/discount-breakdown.tsx +1 -1
  115. package/templates/storefront-nextjs-shadcn/components/discount/discount-code-input.tsx +3 -3
  116. package/templates/storefront-nextjs-shadcn/components/filters/range-slider-filter.tsx +5 -5
  117. package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-input.tsx +2 -2
  118. package/templates/storefront-nextjs-shadcn/components/home/category-grid.tsx +2 -1
  119. package/templates/storefront-nextjs-shadcn/components/home/collection-card.fragment.graphql +21 -0
  120. package/templates/storefront-nextjs-shadcn/components/home/featured-collections.tsx +2 -12
  121. package/templates/storefront-nextjs-shadcn/components/home/index.ts +0 -1
  122. package/templates/storefront-nextjs-shadcn/components/hydrated.tsx +24 -0
  123. package/templates/storefront-nextjs-shadcn/components/layout/breadcrumbs.tsx +4 -4
  124. package/templates/storefront-nextjs-shadcn/components/layout/category-node.fragment.graphql +22 -0
  125. package/templates/storefront-nextjs-shadcn/components/layout/currency-selector.tsx +2 -2
  126. package/templates/storefront-nextjs-shadcn/components/layout/header.tsx +33 -23
  127. package/templates/storefront-nextjs-shadcn/components/loyalty/points-balance.tsx +2 -11
  128. package/templates/storefront-nextjs-shadcn/components/loyalty/points-history.tsx +8 -25
  129. package/templates/storefront-nextjs-shadcn/components/loyalty/referral-section.tsx +10 -19
  130. package/templates/storefront-nextjs-shadcn/components/loyalty/rewards-catalog.tsx +17 -41
  131. package/templates/storefront-nextjs-shadcn/components/loyalty/tier-progress.tsx +2 -29
  132. package/templates/storefront-nextjs-shadcn/components/order/index.ts +6 -1
  133. package/templates/storefront-nextjs-shadcn/components/product/b2b-price-display.tsx +3 -1
  134. package/templates/storefront-nextjs-shadcn/components/product/filter-active-pills.tsx +69 -0
  135. package/templates/storefront-nextjs-shadcn/components/product/filter-mobile-sheet.tsx +84 -0
  136. package/templates/storefront-nextjs-shadcn/components/product/filter-price-range.tsx +138 -0
  137. package/templates/storefront-nextjs-shadcn/components/product/index.ts +9 -2
  138. package/templates/storefront-nextjs-shadcn/components/product/product-card.fragment.graphql +49 -0
  139. package/templates/storefront-nextjs-shadcn/components/product/product-card.tsx +3 -31
  140. package/templates/storefront-nextjs-shadcn/components/product/product-detail.fragment.graphql +52 -0
  141. package/templates/storefront-nextjs-shadcn/components/product/product-filters.tsx +176 -123
  142. package/templates/storefront-nextjs-shadcn/components/product/product-grid.tsx +3 -5
  143. package/templates/storefront-nextjs-shadcn/components/product/product-image.tsx +2 -2
  144. package/templates/storefront-nextjs-shadcn/components/product/product-price.tsx +2 -2
  145. package/templates/storefront-nextjs-shadcn/components/product/product-reviews.tsx +5 -4
  146. package/templates/storefront-nextjs-shadcn/components/product/product-sort.tsx +19 -7
  147. package/templates/storefront-nextjs-shadcn/components/product/product-variant-selector.tsx +8 -23
  148. package/templates/storefront-nextjs-shadcn/components/product/product-variant.fragment.graphql +51 -0
  149. package/templates/storefront-nextjs-shadcn/components/product/review-card.tsx +1 -1
  150. package/templates/storefront-nextjs-shadcn/components/product/review-form.tsx +1 -7
  151. package/templates/storefront-nextjs-shadcn/components/product/savings-display.tsx +17 -2
  152. package/templates/storefront-nextjs-shadcn/components/product/similar-products.tsx +3 -2
  153. package/templates/storefront-nextjs-shadcn/components/providers/index.ts +1 -1
  154. package/templates/storefront-nextjs-shadcn/components/providers/stores-provider.tsx +30 -0
  155. package/templates/storefront-nextjs-shadcn/components/providers/theme-provider.tsx +1 -1
  156. package/templates/storefront-nextjs-shadcn/components/returns/index.ts +2 -2
  157. package/templates/storefront-nextjs-shadcn/components/returns/return-request-form.tsx +3 -2
  158. package/templates/storefront-nextjs-shadcn/components/search/search-results.tsx +3 -2
  159. package/templates/storefront-nextjs-shadcn/components/ui/form.tsx +174 -0
  160. package/templates/storefront-nextjs-shadcn/components/ui/index.ts +30 -2
  161. package/templates/storefront-nextjs-shadcn/components/ui/progress.tsx +40 -0
  162. package/templates/storefront-nextjs-shadcn/components/ui/sheet.tsx +107 -0
  163. package/templates/storefront-nextjs-shadcn/components/ui/slider.tsx +33 -0
  164. package/templates/storefront-nextjs-shadcn/components/ui/textarea.tsx +24 -0
  165. package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-icon.tsx +3 -1
  166. package/templates/storefront-nextjs-shadcn/generated/graphql.ts +12779 -0
  167. package/templates/storefront-nextjs-shadcn/graphql/custom.example.graphql +159 -0
  168. package/templates/storefront-nextjs-shadcn/hooks/index.ts +2 -0
  169. package/templates/storefront-nextjs-shadcn/hooks/use-auth-sync.ts +42 -0
  170. package/templates/storefront-nextjs-shadcn/hooks/use-auth.ts +17 -295
  171. package/templates/storefront-nextjs-shadcn/hooks/use-cart-actions.ts +51 -19
  172. package/templates/storefront-nextjs-shadcn/hooks/use-cart-sync.ts +13 -9
  173. package/templates/storefront-nextjs-shadcn/lib/auth/routes.ts +4 -17
  174. package/templates/storefront-nextjs-shadcn/lib/graphql/client.ts +22 -99
  175. package/templates/storefront-nextjs-shadcn/lib/graphql/config.ts +32 -0
  176. package/templates/storefront-nextjs-shadcn/lib/graphql/fragments.ts +34 -0
  177. package/templates/storefront-nextjs-shadcn/lib/graphql/hooks.ts +687 -632
  178. package/templates/storefront-nextjs-shadcn/lib/graphql/query-keys.ts +86 -0
  179. package/templates/storefront-nextjs-shadcn/lib/graphql/server.ts +131 -182
  180. package/templates/storefront-nextjs-shadcn/lib/graphql/types.ts +62 -0
  181. package/templates/storefront-nextjs-shadcn/lib/theme/theme-config.ts +0 -17
  182. package/templates/storefront-nextjs-shadcn/next-env.d.ts +6 -0
  183. package/templates/storefront-nextjs-shadcn/package.dev.json +1 -3
  184. package/templates/storefront-nextjs-shadcn/package.json +12 -13
  185. package/templates/storefront-nextjs-shadcn/package.json.template +6 -7
  186. package/templates/storefront-nextjs-shadcn/proxy.ts +3 -4
  187. package/templates/storefront-nextjs-shadcn/stores/cart-store.ts +41 -39
  188. package/templates/storefront-nextjs-shadcn/stores/checkout-store.ts +64 -75
  189. package/templates/storefront-nextjs-shadcn/stores/wishlist-store.ts +178 -177
  190. package/templates/storefront-nextjs-shadcn/tsconfig.json +23 -5
  191. package/templates/storefront-nextjs-shadcn/wrangler.toml +4 -0
  192. package/templates/storefront-nextjs-shadcn/CART_INTEGRATION.md +0 -282
  193. package/templates/storefront-nextjs-shadcn/GRAPHQL_DOCUMENT_NAMES.md +0 -190
  194. package/templates/storefront-nextjs-shadcn/GRAPHQL_ERROR_HANDLING.md +0 -263
  195. package/templates/storefront-nextjs-shadcn/GRAPHQL_FIXES_SUMMARY.md +0 -135
  196. package/templates/storefront-nextjs-shadcn/GRAPHQL_INTEGRATION_COMPLETE.md +0 -142
  197. package/templates/storefront-nextjs-shadcn/INTEGRATION_CHECKLIST.md +0 -448
  198. package/templates/storefront-nextjs-shadcn/PRODUCT_DETAIL_PAGE_IMPLEMENTATION.md +0 -307
  199. package/templates/storefront-nextjs-shadcn/THEME_CUSTOMIZATION.md +0 -245
  200. package/templates/storefront-nextjs-shadcn/components/providers/currency-provider.tsx +0 -103
  201. package/templates/storefront-nextjs-shadcn/graphql/collections.example.ts +0 -168
  202. package/templates/storefront-nextjs-shadcn/graphql/products.example.ts +0 -160
  203. package/templates/storefront-nextjs-shadcn/lib/auth/cookies.ts +0 -220
  204. package/templates/storefront-nextjs-shadcn/lib/config.ts +0 -46
  205. package/templates/storefront-nextjs-shadcn/lib/currency/IMPLEMENTATION_SUMMARY.md +0 -254
  206. package/templates/storefront-nextjs-shadcn/lib/currency/README.md +0 -464
  207. package/templates/storefront-nextjs-shadcn/lib/currency/cookie-manager.test.ts +0 -328
  208. package/templates/storefront-nextjs-shadcn/lib/currency/cookie-manager.ts +0 -295
  209. package/templates/storefront-nextjs-shadcn/lib/currency/index.ts +0 -27
  210. package/templates/storefront-nextjs-shadcn/lib/format.ts +0 -226
  211. package/templates/storefront-nextjs-shadcn/lib/hooks.ts +0 -30
  212. package/templates/storefront-nextjs-shadcn/stores/auth-store.ts +0 -66
  213. package/templates/storefront-nextjs-shadcn/stores/currency-store.ts +0 -103
@@ -1,45 +1,62 @@
1
- import { CodegenConfig } from '@graphql-codegen/cli';
2
- import { loadEnvConfig } from '@next/env';
3
-
4
- // Load environment variables from .env.local
5
- loadEnvConfig(process.cwd());
6
-
7
- const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
8
- const shopSlug = process.env.NEXT_PUBLIC_SHOP_SLUG || 'test-shop';
1
+ import type { CodegenConfig } from '@graphql-codegen/cli';
9
2
 
3
+ /**
4
+ * GraphQL Code Generator Configuration
5
+ *
6
+ * Uses `client-preset` with `documentMode: 'string'` (TypedDocumentString).
7
+ * This generates lightweight string-based documents instead of parsed AST objects,
8
+ * eliminating the need for the `graphql` package at runtime.
9
+ *
10
+ * Schema and operations come from @doswiftly/storefront-operations (SSOT).
11
+ * No running backend needed — schema is synced as a file.
12
+ *
13
+ * Architecture decision: https://github.com/dotansimha/graphql-typed-document-node
14
+ * Inspired by: Saleor Storefront (client-preset + TypedDocumentString)
15
+ *
16
+ * To update schema after backend changes:
17
+ * cd packages/@doswiftly/storefront-operations && pnpm run sync
18
+ */
10
19
  const config: CodegenConfig = {
11
- // Schema from live API (introspection)
12
- schema: [
13
- {
14
- [`${apiUrl}/storefront/graphql`]: {
15
- headers: {
16
- 'X-Shop-Slug': shopSlug,
17
- },
18
- },
19
- },
20
- ],
20
+ // Schema from storefront-operations package (no introspection needed!)
21
+ schema: 'node_modules/@doswiftly/storefront-operations/schema.graphql',
21
22
 
22
- // Operations: Backend SSOT + Developer Custom Queries
23
+ // Operations: Backend SSOT + Developer Custom Queries + Colocated Component Fragments
23
24
  documents: [
24
- 'node_modules/@doswiftly/storefront-operations/**/*.graphql', // Backend operations
25
- 'graphql/**/*.{ts,tsx}', // Custom queries in code
25
+ 'node_modules/@doswiftly/storefront-operations/**/*.graphql',
26
+ 'graphql/**/*.graphql',
27
+ 'components/**/*.fragment.graphql',
26
28
  ],
27
29
 
28
- // Output configuration
29
30
  generates: {
30
- 'generated/graphql.ts': {
31
- plugins: [
32
- 'typescript', // Generate TS types
33
- 'typescript-operations', // Generate operation types
34
- 'typed-document-node', // Generate TypedDocumentNode
35
- ],
31
+ 'generated/': {
32
+ preset: 'client',
36
33
  config: {
37
- skipTypename: false,
38
- enumsAsTypes: true,
34
+ // TypedDocumentString: lightweight string wrapper instead of parsed AST
35
+ // Eliminates `graphql` package from runtime bundle (~40kb savings)
36
+ documentMode: 'string',
37
+
38
+ // Scalar mappings
39
39
  scalars: {
40
40
  DateTime: 'string',
41
- JSON: 'Record<string, any>',
41
+ Decimal: 'string',
42
+ JSON: 'Record<string, unknown>',
42
43
  },
44
+
45
+ // Keep __typename for connection normalization and cache keys
46
+ skipTypename: false,
47
+
48
+ // Readable enum types (union of string literals)
49
+ enumsAsTypes: true,
50
+
51
+ // Deduplicate fragments shared across queries
52
+ dedupeFragments: true,
53
+
54
+ // Use `import type` for cleaner output
55
+ useTypeImports: true,
56
+ },
57
+ presetConfig: {
58
+ // Disable fragment masking — simpler DX, developer sees all fields
59
+ fragmentMasking: false,
43
60
  },
44
61
  },
45
62
  },
@@ -0,0 +1,36 @@
1
+ # Fragment for CustomerInfo / AccountDashboard components.
2
+ #
3
+ # Customer profile data for account pages.
4
+ # Does NOT include orders or addresses (fetch separately).
5
+ #
6
+ # Usage in components:
7
+ # import type { CustomerInfoFieldsFragment } from '@/generated/graphql';
8
+ # interface Props { customer: CustomerInfoFieldsFragment }
9
+
10
+ fragment CustomerInfoFields on Customer {
11
+ id
12
+ email
13
+ firstName
14
+ lastName
15
+ displayName
16
+ phone
17
+ emailVerified
18
+ ordersCount
19
+ totalSpent {
20
+ amount
21
+ currencyCode
22
+ }
23
+ defaultAddress {
24
+ id
25
+ firstName
26
+ lastName
27
+ address1
28
+ address2
29
+ city
30
+ province
31
+ zip
32
+ country
33
+ phone
34
+ }
35
+ createdAt
36
+ }
@@ -39,7 +39,7 @@ export interface OrderDetailsData {
39
39
  tax: string;
40
40
  total: string;
41
41
  currency: string;
42
- shippingAddress: OrderAddress;
42
+ shippingAddress?: OrderAddress;
43
43
  billingAddress?: OrderAddress;
44
44
  trackingNumber?: string;
45
45
  estimatedDelivery?: string;
@@ -237,12 +237,14 @@ export function OrderDetails({ order, className }: OrderDetailsProps) {
237
237
 
238
238
  {/* Addresses */}
239
239
  <div className="grid gap-6 md:grid-cols-2">
240
+ {order.shippingAddress && (
240
241
  <Card className="p-6">
241
242
  <h3 className="text-lg font-semibold text-foreground mb-4">
242
243
  Shipping Address
243
244
  </h3>
244
245
  {formatAddress(order.shippingAddress)}
245
246
  </Card>
247
+ )}
246
248
 
247
249
  {order.billingAddress && (
248
250
  <Card className="p-6">
@@ -4,20 +4,11 @@ import Link from "next/link";
4
4
  import { Package, ChevronRight } from "lucide-react";
5
5
  import { Badge } from "@/components/ui/badge";
6
6
  import { Button } from "@/components/ui/button";
7
- import { formatAmount } from "@/lib/format";
8
-
9
- export interface Order {
10
- id: string;
11
- orderNumber: string;
12
- date: string;
13
- status: "delivered" | "shipped" | "processing" | "cancelled";
14
- total: string;
15
- currency: string;
16
- itemCount: number;
17
- }
7
+ import { formatAmount } from "@doswiftly/storefront-sdk";
8
+ import type { OrderSummaryFields } from "@/lib/graphql/fragments";
18
9
 
19
10
  export interface OrderHistoryProps {
20
- orders: Order[];
11
+ orders: OrderSummaryFields[];
21
12
  className?: string;
22
13
  }
23
14
 
@@ -25,17 +16,28 @@ export interface OrderHistoryProps {
25
16
  * OrderHistory - Display list of customer orders
26
17
  */
27
18
  export function OrderHistory({ orders, className }: OrderHistoryProps) {
28
- const getStatusBadge = (status: Order["status"]) => {
29
- const variants: Record<Order["status"], "default" | "secondary" | "success" | "warning"> = {
30
- delivered: "success",
31
- shipped: "default",
32
- processing: "warning",
33
- cancelled: "secondary",
19
+ const getStatusBadge = (status: string) => {
20
+ const variants: Record<string, "default" | "secondary" | "success" | "warning"> = {
21
+ DELIVERED: "success",
22
+ FULFILLED: "success",
23
+ IN_TRANSIT: "default",
24
+ PARTIALLY_FULFILLED: "warning",
25
+ UNFULFILLED: "warning",
26
+ RETURNED: "secondary",
27
+ };
28
+
29
+ const labels: Record<string, string> = {
30
+ DELIVERED: "Delivered",
31
+ FULFILLED: "Fulfilled",
32
+ IN_TRANSIT: "Shipped",
33
+ PARTIALLY_FULFILLED: "Partially Fulfilled",
34
+ UNFULFILLED: "Processing",
35
+ RETURNED: "Returned",
34
36
  };
35
37
 
36
38
  return (
37
- <Badge variant={variants[status]}>
38
- {status.charAt(0).toUpperCase() + status.slice(1)}
39
+ <Badge variant={variants[status] ?? "secondary"}>
40
+ {labels[status] ?? status}
39
41
  </Badge>
40
42
  );
41
43
  };
@@ -80,12 +82,12 @@ export function OrderHistory({ orders, className }: OrderHistoryProps) {
80
82
  <h3 className="text-lg font-semibold text-foreground">
81
83
  Order {order.orderNumber}
82
84
  </h3>
83
- {getStatusBadge(order.status)}
85
+ {getStatusBadge(order.fulfillmentStatus)}
84
86
  </div>
85
87
  <div className="space-y-1 text-sm text-muted-foreground">
86
- <p>Placed on {formatDate(order.date)}</p>
88
+ <p>Placed on {formatDate(order.processedAt)}</p>
87
89
  <p>
88
- {order.itemCount} {order.itemCount === 1 ? "item" : "items"}
90
+ {order.lineItemsCount} {order.lineItemsCount === 1 ? "item" : "items"}
89
91
  </p>
90
92
  </div>
91
93
  </div>
@@ -93,7 +95,7 @@ export function OrderHistory({ orders, className }: OrderHistoryProps) {
93
95
  <div className="text-right">
94
96
  <p className="text-sm text-muted-foreground">Total</p>
95
97
  <p className="text-lg font-semibold text-foreground">
96
- {formatAmount(order.total, order.currency)}
98
+ {formatAmount(order.totalPrice.amount, order.totalPrice.currencyCode)}
97
99
  </p>
98
100
  </div>
99
101
  <ChevronRight className="h-5 w-5 text-muted-foreground" />
@@ -0,0 +1,36 @@
1
+ # Fragment for OrderCard component in account pages.
2
+ #
3
+ # Minimal order data for listing view. Full order details
4
+ # require the Order fragment from storefront-operations.
5
+ #
6
+ # Usage in components:
7
+ # import type { OrderSummaryFieldsFragment } from '@/generated/graphql';
8
+ # interface Props { order: OrderSummaryFieldsFragment }
9
+
10
+ fragment OrderSummaryFields on Order {
11
+ id
12
+ orderNumber
13
+ status
14
+ financialStatus
15
+ fulfillmentStatus
16
+ processedAt
17
+ lineItemsCount
18
+ totalPrice {
19
+ amount
20
+ currencyCode
21
+ }
22
+ subtotalPrice {
23
+ amount
24
+ currencyCode
25
+ }
26
+ totalShipping {
27
+ amount
28
+ currencyCode
29
+ }
30
+ shippingAddress {
31
+ firstName
32
+ lastName
33
+ city
34
+ country
35
+ }
36
+ }
@@ -6,25 +6,24 @@ import { useRouter } from "next/navigation";
6
6
  import { User, LogOut, Package, MapPin, Settings } from "lucide-react";
7
7
  import { Button } from "@/components/ui/button";
8
8
  import { cn } from "@/lib/utils";
9
+ import { useAuth } from "@/hooks/use-auth";
9
10
 
10
11
  export interface AccountMenuProps {
11
12
  customerName?: string;
12
13
  className?: string;
13
14
  }
14
15
 
15
- /**
16
- * AccountMenu - User account dropdown menu
17
- *
18
- * Shows user profile and quick links to account pages
19
- */
20
16
  export function AccountMenu({ customerName, className }: AccountMenuProps) {
21
17
  const router = useRouter();
22
18
  const [isOpen, setIsOpen] = useState(false);
23
19
  const menuRef = useRef<HTMLDivElement>(null);
20
+ const { logout, isLoggingOut } = useAuth();
24
21
 
25
22
  const handleLogout = async () => {
26
- await fetch("/api/auth/clear-token", { method: "POST" });
27
- router.push("/");
23
+ const result = await logout();
24
+ if (result.success) {
25
+ router.push("/");
26
+ }
28
27
  setIsOpen(false);
29
28
  };
30
29
 
@@ -119,10 +118,11 @@ export function AccountMenu({ customerName, className }: AccountMenuProps) {
119
118
  <div className="border-t border-border py-2">
120
119
  <button
121
120
  onClick={handleLogout}
122
- className="flex w-full items-center gap-3 px-4 py-2 text-sm text-destructive hover:bg-muted transition-colors"
121
+ disabled={isLoggingOut}
122
+ className="flex w-full items-center gap-3 px-4 py-2 text-sm text-destructive hover:bg-muted transition-colors disabled:opacity-50"
123
123
  >
124
124
  <LogOut className="h-4 w-4" />
125
- Sign Out
125
+ {isLoggingOut ? "Signing out..." : "Sign Out"}
126
126
  </button>
127
127
  </div>
128
128
  </div>
@@ -6,9 +6,8 @@ import Link from "next/link";
6
6
  import { z } from "zod";
7
7
  import { Button } from "@/components/ui/button";
8
8
  import { Input } from "@/components/ui/input";
9
- import { useCustomerLogin } from "@/lib/graphql/hooks";
9
+ import { useAuth } from "@/hooks/use-auth";
10
10
 
11
- // Zod validation schema for login form
12
11
  const loginSchema = z.object({
13
12
  email: z
14
13
  .string()
@@ -27,11 +26,6 @@ export interface LoginFormProps {
27
26
  redirectTo?: string;
28
27
  }
29
28
 
30
- /**
31
- * LoginForm - Reusable login form component with Zod validation
32
- *
33
- * Validates user input before submission and displays field-level errors
34
- */
35
29
  export function LoginForm({ onSuccess, redirectTo }: LoginFormProps) {
36
30
  const router = useRouter();
37
31
  const searchParams = useSearchParams();
@@ -42,18 +36,16 @@ export function LoginForm({ onSuccess, redirectTo }: LoginFormProps) {
42
36
  const [error, setError] = useState("");
43
37
  const [fieldErrors, setFieldErrors] = useState<Partial<Record<keyof LoginFormData, string>>>({});
44
38
 
45
- const loginMutation = useCustomerLogin();
39
+ const { login, isLoggingIn } = useAuth();
46
40
 
47
41
  const handleSubmit = async (e: React.FormEvent) => {
48
42
  e.preventDefault();
49
43
  setError("");
50
44
  setFieldErrors({});
51
45
 
52
- // Validate form data with Zod
53
46
  const result = loginSchema.safeParse({ email, password });
54
47
 
55
48
  if (!result.success) {
56
- // Extract field-level errors from Zod validation
57
49
  const errors: Partial<Record<keyof LoginFormData, string>> = {};
58
50
  result.error.errors.forEach((err) => {
59
51
  if (err.path[0]) {
@@ -65,37 +57,19 @@ export function LoginForm({ onSuccess, redirectTo }: LoginFormProps) {
65
57
  }
66
58
 
67
59
  try {
68
- const { customerAccessTokenCreate } = await loginMutation.mutateAsync({
69
- input: { email, password },
70
- });
60
+ const loginResult = await login(email, password);
71
61
 
72
- // Check for user errors first
73
- const userErrors = customerAccessTokenCreate?.userErrors || [];
74
-
75
- if (userErrors.length > 0) {
76
- setError(userErrors[0].message || "Login failed");
62
+ if (!loginResult.success) {
63
+ setError(loginResult.userErrors[0]?.message || "Login failed");
77
64
  return;
78
65
  }
79
66
 
80
- if (customerAccessTokenCreate?.customerAccessToken) {
81
- // Store token in httpOnly cookie via API route
82
- await fetch("/api/auth/set-token", {
83
- method: "POST",
84
- headers: { "Content-Type": "application/json" },
85
- body: JSON.stringify({
86
- token: customerAccessTokenCreate.customerAccessToken.accessToken,
87
- }),
88
- });
89
-
90
- if (onSuccess) {
91
- onSuccess();
92
- } else {
93
- router.push(defaultRedirect);
94
- }
67
+ if (onSuccess) {
68
+ onSuccess();
95
69
  } else {
96
- setError("Login failed. Please check your credentials.");
70
+ router.push(defaultRedirect);
97
71
  }
98
- } catch (err) {
72
+ } catch {
99
73
  setError("An error occurred. Please try again.");
100
74
  }
101
75
  };
@@ -169,9 +143,9 @@ export function LoginForm({ onSuccess, redirectTo }: LoginFormProps) {
169
143
  <Button
170
144
  type="submit"
171
145
  className="w-full"
172
- disabled={loginMutation.isPending}
146
+ disabled={isLoggingIn}
173
147
  >
174
- {loginMutation.isPending ? "Signing in..." : "Sign In"}
148
+ {isLoggingIn ? "Signing in..." : "Sign In"}
175
149
  </Button>
176
150
 
177
151
  <div className="text-center text-sm text-muted-foreground">
@@ -4,9 +4,13 @@ import { useState } from "react";
4
4
  import { useRouter } from "next/navigation";
5
5
  import Link from "next/link";
6
6
  import { z } from "zod";
7
- import { useMutation } from "@tanstack/react-query";
8
- import { getGraphQLClient } from "@/lib/graphql/client";
9
- import { CustomerCreateDocument } from "@/generated/graphql";
7
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
8
+ import { useExecute } from "@/lib/graphql/client";
9
+ import { CustomerCreateDocument, type CustomerCreateMutation } from "@/generated/graphql";
10
+ import { useAuthStore } from "@doswiftly/storefront-sdk/react";
11
+ import { createAuthTokenClient } from "@doswiftly/storefront-sdk";
12
+
13
+ const { setToken: setAuthToken } = createAuthTokenClient();
10
14
  import { Button } from "@/components/ui/button";
11
15
  import { Input } from "@/components/ui/input";
12
16
 
@@ -58,7 +62,9 @@ export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFor
58
62
  const [error, setError] = useState("");
59
63
  const [fieldErrors, setFieldErrors] = useState<Partial<Record<keyof RegisterFormData, string>>>({});
60
64
 
61
- const client = getGraphQLClient();
65
+ const queryClient = useQueryClient();
66
+ const { setAuth } = useAuthStore();
67
+ const execute = useExecute();
62
68
 
63
69
  const registerMutation = useMutation({
64
70
  mutationFn: async (input: {
@@ -67,7 +73,10 @@ export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFor
67
73
  firstName: string;
68
74
  lastName: string;
69
75
  }) => {
70
- return client.request(CustomerCreateDocument, { input });
76
+ return execute<CustomerCreateMutation>(
77
+ CustomerCreateDocument.toString(),
78
+ { input },
79
+ );
71
80
  },
72
81
  });
73
82
 
@@ -105,8 +114,7 @@ export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFor
105
114
  });
106
115
 
107
116
  // Check for user errors first
108
- const userErrors = mutationResult?.customerCreate?.userErrors ||
109
- mutationResult?.customerCreate?.customerUserErrors || [];
117
+ const userErrors = mutationResult?.customerCreate?.userErrors || [];
110
118
 
111
119
  if (userErrors.length > 0) {
112
120
  setError(userErrors[0].message || "Registration failed");
@@ -114,28 +122,34 @@ export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFor
114
122
  }
115
123
 
116
124
  if (mutationResult?.customerCreate?.customer) {
117
- // Registration successful - token is already in the response
118
125
  const accessToken = mutationResult?.customerCreate?.customerAccessToken?.accessToken;
119
-
126
+ const customer = mutationResult.customerCreate.customer;
127
+
120
128
  if (accessToken) {
121
- // Store token in httpOnly cookie via API route
122
- const tokenResponse = await fetch("/api/auth/set-token", {
123
- method: "POST",
124
- headers: { "Content-Type": "application/json" },
125
- body: JSON.stringify({ token: accessToken }),
126
- });
129
+ // Store token in Zustand store (for client-side GraphQL requests)
130
+ setAuth(
131
+ {
132
+ id: customer.id,
133
+ email: customer.email,
134
+ firstName: customer.firstName || undefined,
135
+ lastName: customer.lastName || undefined,
136
+ phone: customer.phone || undefined,
137
+ },
138
+ accessToken,
139
+ );
140
+
141
+ // Store token in httpOnly cookie (for SSR/middleware)
142
+ await setAuthToken(accessToken);
143
+
144
+ // Invalidate queries so authenticated data loads fresh
145
+ queryClient.invalidateQueries();
127
146
 
128
- if (tokenResponse.ok) {
129
- if (onSuccess) {
130
- onSuccess();
131
- } else {
132
- router.push(redirectTo);
133
- }
147
+ if (onSuccess) {
148
+ onSuccess();
134
149
  } else {
135
- setError("Registration successful but session setup failed. Please try logging in.");
150
+ router.push(redirectTo);
136
151
  }
137
152
  } else {
138
- // No token in response - redirect to login
139
153
  setError("Registration successful! Please log in with your credentials.");
140
154
  setTimeout(() => router.push("/auth/login"), 2000);
141
155
  }
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { useEffect } from "react";
4
+ import { useRouter } from "next/navigation";
4
5
  import { X, ShoppingBag, Loader2 } from "lucide-react";
5
6
  import { useCartStore } from "@/stores/cart-store";
6
7
  import { useCartSync } from "@/hooks/use-cart-sync";
@@ -22,6 +23,8 @@ export interface CartDrawerProps {
22
23
  * Mutations go through useCartActions (server-only).
23
24
  */
24
25
  export function CartDrawer({ className }: CartDrawerProps) {
26
+ const router = useRouter();
27
+
25
28
  // UI state from Zustand
26
29
  const { isOpen, closeCart } = useCartStore();
27
30
 
@@ -45,9 +48,7 @@ export function CartDrawer({ className }: CartDrawerProps) {
45
48
 
46
49
  const handleCheckout = () => {
47
50
  closeCart();
48
- if (typeof window !== 'undefined') {
49
- window.location.href = "/checkout";
50
- }
51
+ router.push("/checkout");
51
52
  };
52
53
 
53
54
  if (!isOpen) return null;
@@ -3,6 +3,7 @@
3
3
  import { useRouter } from "next/navigation";
4
4
  import { ShoppingCart } from "lucide-react";
5
5
  import { useCartSync } from "@/hooks/use-cart-sync";
6
+ import { useHydrated } from "@doswiftly/storefront-sdk/react";
6
7
  import { Button } from "@/components/ui/button";
7
8
  import { cn } from "@/lib/utils";
8
9
 
@@ -15,11 +16,13 @@ export interface CartIconProps {
15
16
  /**
16
17
  * CartIcon - Cart button with item count badge
17
18
  *
18
- * Client Component that reads cart count from server (source of truth)
19
+ * Client Component that reads cart count from server (source of truth).
20
+ * Badge is guarded by isHydrated to prevent hydration mismatch.
19
21
  */
20
22
  export function CartIcon({ className, navigateToCart = true }: CartIconProps) {
21
23
  const router = useRouter();
22
- const { totalQuantity, isLoading } = useCartSync();
24
+ const isHydrated = useHydrated();
25
+ const { totalQuantity } = useCartSync();
23
26
 
24
27
  const handleClick = () => {
25
28
  if (navigateToCart) {
@@ -33,12 +36,12 @@ export function CartIcon({ className, navigateToCart = true }: CartIconProps) {
33
36
  size="sm"
34
37
  onClick={handleClick}
35
38
  className={cn("relative p-2", className)}
36
- aria-label={`Shopping cart with ${totalQuantity} items`}
39
+ aria-label={`Shopping cart${isHydrated && totalQuantity > 0 ? ` with ${totalQuantity} items` : ""}`}
37
40
  >
38
41
  <ShoppingCart className="h-5 w-5" />
39
42
 
40
- {/* Badge */}
41
- {totalQuantity > 0 && (
43
+ {/* Badge — only rendered after hydration to prevent mismatch */}
44
+ {isHydrated && totalQuantity > 0 && (
42
45
  <span className="absolute -right-1 -top-1 flex h-5 w-5 items-center justify-center rounded-full bg-primary text-xs font-medium text-primary-foreground">
43
46
  {totalQuantity > 99 ? "99+" : totalQuantity}
44
47
  </span>
@@ -6,7 +6,7 @@ import { ProductImage } from "@/components/product/product-image";
6
6
  import { ProductQuantitySelector } from "@/components/product/product-quantity-selector";
7
7
  import { Button } from "@/components/ui/button";
8
8
  import { cn } from "@/lib/utils";
9
- import { formatPrice, formatAmount } from "@/lib/format";
9
+ import { formatPrice, formatAmount } from "@doswiftly/storefront-sdk";
10
10
  import type { CartItemData } from "@/hooks/use-cart-sync";
11
11
 
12
12
  export interface CartItemProps {
@@ -0,0 +1,53 @@
1
+ # Fragment for CartItem / CartLine component.
2
+ #
3
+ # Maps to the CartItemData interface in use-cart-sync.ts.
4
+ # Used by: CartDrawer, CartPage, MiniCart.
5
+ #
6
+ # Usage in components:
7
+ # import type { CartLineFieldsFragment } from '@/generated/graphql';
8
+ # interface Props { line: CartLineFieldsFragment }
9
+ #
10
+ # Note: This is a UI-focused subset. The full Cart fragment
11
+ # from storefront-operations includes cost aggregates and
12
+ # discount allocations for the cart summary.
13
+
14
+ fragment CartLineFields on CartLine {
15
+ id
16
+ quantity
17
+ productId
18
+ productTitle
19
+ productHandle
20
+ productType
21
+ merchandise {
22
+ id
23
+ title
24
+ available
25
+ quantityAvailable
26
+ price {
27
+ amount
28
+ currencyCode
29
+ }
30
+ image {
31
+ url
32
+ altText
33
+ }
34
+ selectedOptions {
35
+ name
36
+ value
37
+ }
38
+ }
39
+ cost {
40
+ amountPerQuantity {
41
+ amount
42
+ currencyCode
43
+ }
44
+ totalAmount {
45
+ amount
46
+ currencyCode
47
+ }
48
+ compareAtAmountPerQuantity {
49
+ amount
50
+ currencyCode
51
+ }
52
+ }
53
+ }
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { Button } from "@/components/ui/button";
4
4
  import { cn } from "@/lib/utils";
5
- import { formatAmount } from "@/lib/format";
5
+ import { formatAmount } from "@doswiftly/storefront-sdk";
6
6
 
7
7
  export interface CartSummaryProps {
8
8
  subtotal: number;