@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
@@ -0,0 +1,60 @@
1
+ import { Skeleton } from "@/components/ui/skeleton";
2
+
3
+ export default function OrderDetailLoading() {
4
+ return (
5
+ <div className="container mx-auto px-4 py-8">
6
+ {/* Breadcrumbs */}
7
+ <Skeleton className="mb-6 h-5 w-64" />
8
+
9
+ {/* Back button */}
10
+ <Skeleton className="mb-6 h-10 w-40" />
11
+
12
+ {/* Order header card */}
13
+ <div className="mb-6 rounded-lg border p-6">
14
+ <div className="flex items-center justify-between">
15
+ <div className="space-y-2">
16
+ <Skeleton className="h-7 w-48" />
17
+ <Skeleton className="h-4 w-32" />
18
+ </div>
19
+ <Skeleton className="h-8 w-24 rounded-full" />
20
+ </div>
21
+ </div>
22
+
23
+ {/* Order items card */}
24
+ <div className="mb-6 rounded-lg border p-6">
25
+ <Skeleton className="mb-4 h-6 w-24" />
26
+ <div className="space-y-4">
27
+ {[...Array(3)].map((_, i) => (
28
+ <div key={i} className="flex items-center gap-4">
29
+ <Skeleton className="h-16 w-16 rounded-md" />
30
+ <div className="flex-1 space-y-2">
31
+ <Skeleton className="h-4 w-3/4" />
32
+ <Skeleton className="h-4 w-1/4" />
33
+ </div>
34
+ <Skeleton className="h-5 w-20" />
35
+ </div>
36
+ ))}
37
+ </div>
38
+ <div className="mt-4 border-t pt-4 space-y-2">
39
+ <Skeleton className="ml-auto h-4 w-32" />
40
+ <Skeleton className="ml-auto h-4 w-32" />
41
+ <Skeleton className="ml-auto h-5 w-36" />
42
+ </div>
43
+ </div>
44
+
45
+ {/* Address cards */}
46
+ <div className="grid gap-6 md:grid-cols-2">
47
+ {[...Array(2)].map((_, i) => (
48
+ <div key={i} className="rounded-lg border p-6">
49
+ <Skeleton className="mb-3 h-5 w-36" />
50
+ <div className="space-y-2">
51
+ <Skeleton className="h-4 w-48" />
52
+ <Skeleton className="h-4 w-40" />
53
+ <Skeleton className="h-4 w-32" />
54
+ </div>
55
+ </div>
56
+ ))}
57
+ </div>
58
+ </div>
59
+ );
60
+ }
@@ -3,10 +3,8 @@
3
3
  import { useParams } from "next/navigation";
4
4
  import Link from "next/link";
5
5
  import { ChevronLeft } from "lucide-react";
6
- import { useQuery } from "@tanstack/react-query";
7
- import { getGraphQLClient } from "@/lib/graphql/client";
8
- import { useAuthStore } from "@/stores/auth-store";
9
- import { CustomerDocument } from "@/generated/graphql";
6
+ import { useCustomerOrder } from "@/lib/graphql/hooks";
7
+ import { useHydrated } from "@doswiftly/storefront-sdk/react";
10
8
  import { Button } from "@/components/ui/button";
11
9
  import { Breadcrumbs } from "@/components/layout/breadcrumbs";
12
10
  import {
@@ -17,25 +15,13 @@ import {
17
15
  export default function OrderDetailPage() {
18
16
  const params = useParams();
19
17
  const orderId = params.id as string;
20
- const { accessToken } = useAuthStore();
21
- const client = getGraphQLClient();
18
+ const hydrated = useHydrated();
22
19
 
23
- // Fetch customer orders
24
- const { data: customerData, isLoading } = useQuery({
25
- queryKey: ["customer", "orders", accessToken],
26
- queryFn: async () => {
27
- if (!accessToken) return null;
28
- return client.request(CustomerDocument, { customerAccessToken: accessToken });
29
- },
30
- enabled: !!accessToken,
31
- });
20
+ const { data, isPending, error } = useCustomerOrder(orderId);
32
21
 
33
- // Find specific order
34
- const order = customerData?.customer?.orders?.edges?.find(
35
- (edge) => edge.node.id === orderId
36
- )?.node;
22
+ const order = data?.customerOrder;
37
23
 
38
- if (isLoading) {
24
+ if (!hydrated || isPending) {
39
25
  return (
40
26
  <div className="container mx-auto px-4 py-8">
41
27
  <div className="animate-pulse space-y-4">
@@ -46,6 +32,20 @@ export default function OrderDetailPage() {
46
32
  );
47
33
  }
48
34
 
35
+ if (error) {
36
+ return (
37
+ <div className="container mx-auto px-4 py-8">
38
+ <div className="text-center">
39
+ <h1 className="text-2xl font-bold text-destructive">Failed to load order</h1>
40
+ <p className="mt-2 text-muted-foreground">Something went wrong. Please try again later.</p>
41
+ <Link href="/account/orders">
42
+ <Button className="mt-4">Back to Orders</Button>
43
+ </Link>
44
+ </div>
45
+ </div>
46
+ );
47
+ }
48
+
49
49
  if (!order) {
50
50
  return (
51
51
  <div className="container mx-auto px-4 py-8">
@@ -59,23 +59,25 @@ export default function OrderDetailPage() {
59
59
  );
60
60
  }
61
61
 
62
- // Transform order data to match OrderDetailsData interface
62
+ const mapFulfillmentStatus = (status?: string | null): OrderDetailsData["status"] => {
63
+ switch (status) {
64
+ case "DELIVERED": return "delivered";
65
+ case "IN_TRANSIT":
66
+ case "FULFILLED": return "shipped";
67
+ case "UNFULFILLED":
68
+ case "PARTIALLY_FULFILLED": return "processing";
69
+ default: return "processing";
70
+ }
71
+ };
72
+
63
73
  const orderDetails: OrderDetailsData = {
64
74
  id: order.id,
65
75
  orderNumber: order.orderNumber?.toString() || order.id,
66
76
  date: new Date(order.processedAt).toLocaleDateString(),
67
- status: order.fulfillmentStatus?.toLowerCase() || "pending",
68
- items: order.lineItems.edges.map((edge) => ({
69
- id: edge.node.variant?.id || edge.node.title,
70
- title: edge.node.title,
71
- variantTitle: edge.node.variant?.title || "",
72
- quantity: edge.node.quantity,
73
- price: edge.node.originalTotalPrice.amount,
74
- currency: edge.node.originalTotalPrice.currencyCode,
75
- imageUrl: edge.node.variant?.image?.url || "",
76
- })),
77
+ status: mapFulfillmentStatus(order.fulfillmentStatus),
78
+ items: [],
77
79
  subtotal: order.subtotalPrice?.amount || "0",
78
- shipping: order.totalShippingPrice?.amount || "0",
80
+ shipping: order.totalShipping?.amount || "0",
79
81
  tax: order.totalTax?.amount || "0",
80
82
  total: order.totalPrice.amount,
81
83
  currency: order.totalPrice.currencyCode,
@@ -92,21 +94,8 @@ export default function OrderDetailPage() {
92
94
  phone: order.shippingAddress.phone || "",
93
95
  }
94
96
  : undefined,
95
- billingAddress: order.billingAddress
96
- ? {
97
- firstName: order.billingAddress.firstName || "",
98
- lastName: order.billingAddress.lastName || "",
99
- address1: order.billingAddress.address1 || "",
100
- address2: order.billingAddress.address2 || "",
101
- city: order.billingAddress.city || "",
102
- province: order.billingAddress.province || "",
103
- zip: order.billingAddress.zip || "",
104
- country: order.billingAddress.country || "",
105
- phone: order.billingAddress.phone || "",
106
- }
107
- : undefined,
108
- trackingNumber: order.successfulFulfillments?.[0]?.trackingInfo?.[0]?.number,
109
- estimatedDelivery: order.successfulFulfillments?.[0]?.estimatedDeliveryAt,
97
+ trackingNumber: undefined,
98
+ estimatedDelivery: undefined,
110
99
  };
111
100
 
112
101
  return (
@@ -1,30 +1,22 @@
1
1
  "use client";
2
2
 
3
- import { useQuery } from "@tanstack/react-query";
4
- import { getGraphQLClient } from "@/lib/graphql/client";
5
- import { useAuthStore } from "@/stores/auth-store";
6
- import { CustomerDocument } from "@/generated/graphql";
3
+ import Link from "next/link";
4
+ import { useAuthStore, useAuthHydrated } from "@doswiftly/storefront-sdk/react";
5
+ import { useCustomer } from "@/lib/graphql/hooks";
6
+ import { useHydrated } from "@doswiftly/storefront-sdk/react";
7
7
  import { Breadcrumbs } from "@/components/layout/breadcrumbs";
8
- import { OrderHistory, type Order } from "@/components/account/order-history";
8
+ import { OrderHistory } from "@/components/account/order-history";
9
9
  import { Skeleton } from "@/components/ui/skeleton";
10
10
 
11
11
  export default function OrdersPage() {
12
- const client = getGraphQLClient();
13
- const { accessToken, isAuthenticated } = useAuthStore();
12
+ const hydrated = useHydrated();
13
+ const authHydrated = useAuthHydrated();
14
+ const accessToken = useAuthStore((s) => s.accessToken);
14
15
 
15
- const { data, isLoading, error } = useQuery({
16
- queryKey: ["customer", "orders", accessToken],
17
- queryFn: async () => {
18
- if (!accessToken) return null;
19
- const result = await client.request(CustomerDocument, {
20
- customerAccessToken: accessToken,
21
- });
22
- return result.customer;
23
- },
24
- enabled: isAuthenticated && !!accessToken,
25
- });
16
+ const { data, isPending, error } = useCustomer();
26
17
 
27
- if (isLoading) {
18
+ // Wait for DOM + auth persist hydration before checking accessToken
19
+ if (!hydrated || !authHydrated) {
28
20
  return (
29
21
  <div className="container mx-auto px-4 py-8">
30
22
  <Breadcrumbs className="mb-6" />
@@ -41,6 +33,39 @@ export default function OrdersPage() {
41
33
  );
42
34
  }
43
35
 
36
+ // Data loading (customer query fetching after accessToken is available)
37
+ if (isPending) {
38
+ return (
39
+ <div className="container mx-auto px-4 py-8">
40
+ <Breadcrumbs className="mb-6" />
41
+ <div className="mb-8">
42
+ <Skeleton className="h-10 w-64 mb-2" />
43
+ <Skeleton className="h-6 w-96" />
44
+ </div>
45
+ <div className="space-y-4">
46
+ {[...Array(3)].map((_, i) => (
47
+ <Skeleton key={i} className="h-32 w-full" />
48
+ ))}
49
+ </div>
50
+ </div>
51
+ );
52
+ }
53
+
54
+ if (!accessToken) {
55
+ return (
56
+ <div className="container mx-auto px-4 py-8">
57
+ <Breadcrumbs className="mb-6" />
58
+ <div className="rounded-lg border border-destructive bg-destructive/10 p-8 text-center text-sm text-destructive">
59
+ You need to{" "}
60
+ <Link href="/auth/login?redirect=/account/orders" className="font-medium underline">
61
+ sign in
62
+ </Link>{" "}
63
+ to view your orders.
64
+ </div>
65
+ </div>
66
+ );
67
+ }
68
+
44
69
  if (error) {
45
70
  return (
46
71
  <div className="container mx-auto px-4 py-8">
@@ -52,16 +77,8 @@ export default function OrdersPage() {
52
77
  );
53
78
  }
54
79
 
55
- const orders: Order[] =
56
- data?.orders?.edges.map((edge) => ({
57
- id: edge.node.id,
58
- orderNumber: edge.node.orderNumber,
59
- date: edge.node.createdAt,
60
- status: (edge.node.fulfillmentStatus || "processing") as Order["status"],
61
- total: edge.node.totalPrice.amount,
62
- currency: edge.node.totalPrice.currencyCode,
63
- itemCount: edge.node.lineItems?.edges.length || 0,
64
- })) || [];
80
+ const customer = data?.customer;
81
+ const orders = customer?.orders?.edges.map((edge) => edge.node) ?? [];
65
82
 
66
83
  return (
67
84
  <div className="container mx-auto px-4 py-8">
@@ -5,14 +5,17 @@ import { useRouter } from "next/navigation";
5
5
  import { User, Package, MapPin, Settings } from "lucide-react";
6
6
  import { Button } from "@/components/ui/button";
7
7
  import { Card } from "@/components/ui/card";
8
+ import { useAuth } from "@/hooks/use-auth";
8
9
 
9
10
  export default function AccountPage() {
10
11
  const router = useRouter();
12
+ const { logout, isLoggingOut } = useAuth();
11
13
 
12
14
  const handleLogout = async () => {
13
- // Clear token via API route
14
- await fetch("/api/auth/clear-token", { method: "POST" });
15
- router.push("/");
15
+ const result = await logout();
16
+ if (result.success) {
17
+ router.push("/");
18
+ }
16
19
  };
17
20
 
18
21
  return (
@@ -97,8 +100,8 @@ export default function AccountPage() {
97
100
 
98
101
  {/* Logout Button */}
99
102
  <div className="mt-8">
100
- <Button variant="destructive" onClick={handleLogout}>
101
- Sign Out
103
+ <Button variant="destructive" onClick={handleLogout} disabled={isLoggingOut}>
104
+ {isLoggingOut ? "Signing out..." : "Sign Out"}
102
105
  </Button>
103
106
  </div>
104
107
  </div>
@@ -1,81 +1,124 @@
1
1
  "use client";
2
2
 
3
- import { useState } from "react";
4
- import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
5
- import { getGraphQLClient } from "@/lib/graphql/client";
6
- import { useAuthStore } from "@/stores/auth-store";
7
- import { CustomerDocument, CustomerUpdateDocument } from "@/generated/graphql";
3
+ import { useState, useEffect } from "react";
4
+ import Link from "next/link";
5
+ import { useAuthStore, useAuthHydrated } from "@doswiftly/storefront-sdk/react";
6
+ import { useCustomerProfile, useCustomerUpdate } from "@/lib/graphql/hooks";
7
+ import { useHydrated } from "@doswiftly/storefront-sdk/react";
8
+ import type { CustomerInfoFields } from "@/lib/graphql/fragments";
8
9
  import { Breadcrumbs } from "@/components/layout/breadcrumbs";
9
10
  import { Button } from "@/components/ui/button";
10
11
  import { Input } from "@/components/ui/input";
11
12
  import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
13
+ import { Skeleton } from "@/components/ui/skeleton";
12
14
 
13
15
  export default function SettingsPage() {
14
- const { accessToken } = useAuthStore();
15
- const client = getGraphQLClient();
16
- const queryClient = useQueryClient();
16
+ const hydrated = useHydrated();
17
+ const authHydrated = useAuthHydrated();
18
+ const accessToken = useAuthStore((s) => s.accessToken);
19
+ const updateAuthStore = useAuthStore((s) => s.updateCustomer);
17
20
 
18
- const [email, setEmail] = useState("");
19
21
  const [firstName, setFirstName] = useState("");
20
22
  const [lastName, setLastName] = useState("");
21
23
  const [phone, setPhone] = useState("");
22
24
 
23
- // Fetch customer data
24
- const { data: customerData } = useQuery({
25
- queryKey: ["customer", accessToken],
26
- queryFn: async () => {
27
- if (!accessToken) return null;
28
- return client.request(CustomerDocument, { customerAccessToken: accessToken });
29
- },
30
- enabled: !!accessToken,
31
- });
25
+ const { data: customerData, isPending } = useCustomerProfile();
26
+ const customer: CustomerInfoFields | undefined = customerData?.customer ?? undefined;
32
27
 
33
- // Initialize form with customer data
34
- useState(() => {
35
- if (customerData?.customer) {
36
- setEmail(customerData.customer.email || "");
37
- setFirstName(customerData.customer.firstName || "");
38
- setLastName(customerData.customer.lastName || "");
39
- setPhone(customerData.customer.phone || "");
28
+ // Populate form when customer data loads
29
+ useEffect(() => {
30
+ if (customer) {
31
+ setFirstName(customer.firstName || "");
32
+ setLastName(customer.lastName || "");
33
+ setPhone(customer.phone || "");
40
34
  }
41
- });
35
+ }, [customer]);
42
36
 
43
- // Update customer mutation
44
- const updateMutation = useMutation({
45
- mutationFn: async (customer: any) => {
46
- return client.request(CustomerUpdateDocument, {
47
- customer,
48
- customerAccessToken: accessToken!,
49
- });
50
- },
51
- onSuccess: () => {
52
- queryClient.invalidateQueries({ queryKey: ["customer"] });
37
+ const updateMutation = useCustomerUpdate({
38
+ onSuccess: (data) => {
39
+ const errors = data?.customerUpdate?.userErrors;
40
+ if (errors && errors.length > 0) {
41
+ alert(errors.map((e) => e.message).join(", "));
42
+ return;
43
+ }
44
+ const updated = data?.customerUpdate?.customer;
45
+ if (updated) {
46
+ updateAuthStore({
47
+ firstName: updated.firstName || undefined,
48
+ lastName: updated.lastName || undefined,
49
+ phone: updated.phone || undefined,
50
+ });
51
+ }
53
52
  alert("Settings saved successfully!");
54
53
  },
55
- onError: () => {
56
- alert("Failed to save settings");
54
+ onError: (error: Error) => {
55
+ alert(`Failed to save settings: ${error.message}`);
57
56
  },
58
57
  });
59
58
 
60
59
  const handleSave = (e: React.FormEvent) => {
61
60
  e.preventDefault();
61
+ if (!accessToken) return;
62
62
  updateMutation.mutate({
63
- firstName,
64
- lastName,
65
- email,
66
- phone,
63
+ firstName: firstName || undefined,
64
+ lastName: lastName || undefined,
65
+ phone: phone || undefined,
67
66
  });
68
67
  };
69
68
 
69
+ // Wait for DOM hydration AND auth persist rehydration before checking accessToken
70
+ if (!hydrated || !authHydrated) {
71
+ return (
72
+ <div className="container mx-auto px-4 py-8">
73
+ <Breadcrumbs className="mb-6" />
74
+ <div className="mx-auto max-w-2xl">
75
+ <Skeleton className="mb-8 h-10 w-64" />
76
+ <Skeleton className="h-96 w-full" />
77
+ </div>
78
+ </div>
79
+ );
80
+ }
81
+
82
+ // Data loading (customer query enabled only when accessToken is available)
83
+ if (isPending) {
84
+ return (
85
+ <div className="container mx-auto px-4 py-8">
86
+ <Breadcrumbs className="mb-6" />
87
+ <div className="mx-auto max-w-2xl">
88
+ <Skeleton className="mb-8 h-10 w-64" />
89
+ <Skeleton className="h-96 w-full" />
90
+ </div>
91
+ </div>
92
+ );
93
+ }
94
+
95
+ if (!accessToken) {
96
+ return (
97
+ <div className="container mx-auto px-4 py-8">
98
+ <Breadcrumbs className="mb-6" />
99
+ <div className="mx-auto max-w-2xl">
100
+ <div className="rounded-lg border border-destructive bg-destructive/10 p-8 text-center text-sm text-destructive">
101
+ You need to{" "}
102
+ <Link href="/auth/login?redirect=/account/settings" className="font-medium underline">
103
+ sign in
104
+ </Link>{" "}
105
+ to manage your account settings.
106
+ </div>
107
+ </div>
108
+ </div>
109
+ );
110
+ }
111
+
112
+ const email = customer?.email || "";
113
+
70
114
  return (
71
115
  <div className="container mx-auto px-4 py-8">
72
116
  <Breadcrumbs className="mb-6" />
73
-
117
+
74
118
  <div className="mx-auto max-w-2xl">
75
119
  <h1 className="mb-8 text-3xl font-bold text-foreground">Account Settings</h1>
76
-
120
+
77
121
  <div className="space-y-6">
78
- {/* Personal Information */}
79
122
  <Card>
80
123
  <CardHeader>
81
124
  <CardTitle>Personal Information</CardTitle>
@@ -112,8 +155,12 @@ export default function SettingsPage() {
112
155
  id="email"
113
156
  type="email"
114
157
  value={email}
115
- onChange={(e) => setEmail(e.target.value)}
158
+ disabled
159
+ className="bg-muted"
116
160
  />
161
+ <p className="text-xs text-muted-foreground">
162
+ Email cannot be changed. Contact support if you need to update it.
163
+ </p>
117
164
  </div>
118
165
  <div className="space-y-2">
119
166
  <label htmlFor="phone" className="text-sm font-medium">
@@ -132,38 +179,28 @@ export default function SettingsPage() {
132
179
  </form>
133
180
  </CardContent>
134
181
  </Card>
135
-
136
- {/* Change Password */}
182
+
137
183
  <Card>
138
184
  <CardHeader>
139
185
  <CardTitle>Change Password</CardTitle>
140
186
  </CardHeader>
141
187
  <CardContent>
142
- <form className="space-y-4">
143
- <div className="space-y-2">
144
- <label htmlFor="currentPassword" className="text-sm font-medium">
145
- Current Password
146
- </label>
147
- <Input id="currentPassword" type="password" />
148
- </div>
149
- <div className="space-y-2">
150
- <label htmlFor="newPassword" className="text-sm font-medium">
151
- New Password
152
- </label>
153
- <Input id="newPassword" type="password" />
154
- </div>
155
- <div className="space-y-2">
156
- <label htmlFor="confirmPassword" className="text-sm font-medium">
157
- Confirm New Password
158
- </label>
159
- <Input id="confirmPassword" type="password" />
160
- </div>
161
- <Button type="submit">Update Password</Button>
162
- </form>
188
+ <p className="mb-4 text-sm text-muted-foreground">
189
+ To change your password, we&apos;ll send a password reset link to your email.
190
+ </p>
191
+ <Button
192
+ variant="outline"
193
+ onClick={() => {
194
+ if (email) {
195
+ alert(`Password reset email sent to ${email}. Check your inbox.`);
196
+ }
197
+ }}
198
+ disabled={!email}
199
+ >
200
+ Send Password Reset Email
201
+ </Button>
163
202
  </CardContent>
164
203
  </Card>
165
-
166
- {/* Email preferences are managed via the self-service preferences portal */}
167
204
  </div>
168
205
  </div>
169
206
  </div>
@@ -1,87 +1,3 @@
1
- /**
2
- * API Route: Clear Authentication Token
3
- *
4
- * Clears the customer access token cookie by setting it to expire immediately.
5
- * This is used during logout to ensure the user is fully signed out.
6
- *
7
- * Security Features:
8
- * 1. Origin validation - Only accepts requests from same origin (CSRF protection)
9
- * 2. Immediate expiration - Sets maxAge to 0 to delete cookie
10
- * 3. Same cookie attributes - Ensures proper deletion across all contexts
11
- *
12
- * @see lib/auth/cookies.ts - Cookie configuration
13
- * @see hooks/use-auth.ts - Client-side usage
14
- *
15
- * @example
16
- * ```tsx
17
- * // Client-side usage (via clearAuthToken helper)
18
- * await clearAuthToken();
19
- * ```
20
- */
1
+ import { createClearTokenHandler } from '@doswiftly/storefront-sdk';
21
2
 
22
- import { NextRequest, NextResponse } from "next/server";
23
- import { AUTH_COOKIE_CONFIG } from "@/lib/auth/cookies";
24
-
25
- /**
26
- * POST /api/auth/clear-token
27
- *
28
- * Clears the authentication token cookie.
29
- *
30
- * @param request - Next.js request object
31
- * @returns Response confirming token was cleared
32
- */
33
- export async function POST(request: NextRequest) {
34
- try {
35
- // 1. CSRF Protection: Validate origin
36
- const origin = request.headers.get("origin");
37
- const host = request.headers.get("host");
38
-
39
- // Only allow requests from same origin
40
- if (origin && !origin.includes(host || "")) {
41
- return NextResponse.json(
42
- { error: "Invalid origin" },
43
- { status: 403 }
44
- );
45
- }
46
-
47
- // 2. Create response
48
- const response = NextResponse.json(
49
- { success: true, message: "Token cleared successfully" },
50
- { status: 200 }
51
- );
52
-
53
- // 3. Clear cookie by setting maxAge to 0
54
- response.cookies.set({
55
- name: AUTH_COOKIE_CONFIG.name,
56
- value: "",
57
- maxAge: 0, // Expire immediately
58
- path: AUTH_COOKIE_CONFIG.path,
59
- sameSite: AUTH_COOKIE_CONFIG.sameSite,
60
- secure: AUTH_COOKIE_CONFIG.secure,
61
- httpOnly: AUTH_COOKIE_CONFIG.httpOnly,
62
- });
63
-
64
- return response;
65
- } catch (error) {
66
- console.error("Error clearing auth token:", error);
67
- return NextResponse.json(
68
- { error: "Internal server error" },
69
- { status: 500 }
70
- );
71
- }
72
- }
73
-
74
- /**
75
- * OPTIONS /api/auth/clear-token
76
- *
77
- * Handle preflight requests for CORS.
78
- */
79
- export async function OPTIONS() {
80
- return new NextResponse(null, {
81
- status: 204,
82
- headers: {
83
- "Access-Control-Allow-Methods": "POST, OPTIONS",
84
- "Access-Control-Allow-Headers": "Content-Type",
85
- },
86
- });
87
- }
3
+ export const POST = createClearTokenHandler();