@doswiftly/cli 0.1.18 → 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 (210) 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 +39 -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-nextjs/README.md +16 -12
  22. package/templates/storefront-nextjs/app/account/orders/page.tsx +2 -2
  23. package/templates/storefront-nextjs/app/account/page.tsx +2 -2
  24. package/templates/storefront-nextjs/app/auth/login/page.tsx +1 -1
  25. package/templates/storefront-nextjs/app/auth/register/page.tsx +1 -1
  26. package/templates/storefront-nextjs/app/cart/page.tsx +1 -1
  27. package/templates/storefront-nextjs/app/categories/[slug]/page.tsx +2 -2
  28. package/templates/storefront-nextjs/app/categories/page.tsx +1 -1
  29. package/templates/storefront-nextjs/app/collections/[slug]/page.tsx +1 -1
  30. package/templates/storefront-nextjs/app/collections/page.tsx +1 -1
  31. package/templates/storefront-nextjs/app/page.tsx +1 -1
  32. package/templates/storefront-nextjs/app/products/[slug]/page.tsx +1 -1
  33. package/templates/storefront-nextjs/app/products/page.tsx +2 -2
  34. package/templates/storefront-nextjs/app/search/page.tsx +1 -1
  35. package/templates/storefront-nextjs/components/auth/auth-guard.tsx +1 -1
  36. package/templates/storefront-nextjs/components/commerce/add-to-cart-button.tsx +1 -1
  37. package/templates/storefront-nextjs/components/commerce/cart-icon.tsx +1 -1
  38. package/templates/storefront-nextjs/components/commerce/currency-selector.tsx +2 -2
  39. package/templates/storefront-nextjs/components/commerce/product-filters.tsx +1 -1
  40. package/templates/storefront-nextjs/components/commerce/product-price.tsx +1 -1
  41. package/templates/storefront-nextjs/components/commerce/search-input.tsx +1 -1
  42. package/templates/storefront-nextjs/components/commerce/sort-select.tsx +1 -1
  43. package/templates/storefront-nextjs/components/providers.tsx +1 -1
  44. package/templates/storefront-nextjs/lib/currency.tsx +3 -3
  45. package/templates/storefront-nextjs/lib/format.ts +1 -1
  46. package/templates/storefront-nextjs/lib/graphql-queries.ts +3 -3
  47. package/templates/storefront-nextjs/package.dev.json +1 -1
  48. package/templates/storefront-nextjs/package.json +1 -1
  49. package/templates/storefront-nextjs/package.json.template +1 -1
  50. package/templates/storefront-nextjs-shadcn/.github/workflows/deploy.yml +47 -0
  51. package/templates/storefront-nextjs-shadcn/.github/workflows/preview.yml +47 -0
  52. package/templates/storefront-nextjs-shadcn/CLAUDE.md +148 -35
  53. package/templates/storefront-nextjs-shadcn/README.md +29 -162
  54. package/templates/storefront-nextjs-shadcn/app/account/addresses/page.tsx +98 -91
  55. package/templates/storefront-nextjs-shadcn/app/account/error.tsx +43 -0
  56. package/templates/storefront-nextjs-shadcn/app/account/loading.tsx +19 -0
  57. package/templates/storefront-nextjs-shadcn/app/account/loyalty/page.tsx +53 -162
  58. package/templates/storefront-nextjs-shadcn/app/account/orders/[id]/loading.tsx +60 -0
  59. package/templates/storefront-nextjs-shadcn/app/account/orders/[id]/page.tsx +36 -47
  60. package/templates/storefront-nextjs-shadcn/app/account/orders/page.tsx +46 -29
  61. package/templates/storefront-nextjs-shadcn/app/account/page.tsx +8 -5
  62. package/templates/storefront-nextjs-shadcn/app/account/settings/page.tsx +108 -71
  63. package/templates/storefront-nextjs-shadcn/app/api/auth/clear-token/route.ts +2 -86
  64. package/templates/storefront-nextjs-shadcn/app/api/auth/set-token/route.ts +2 -124
  65. package/templates/storefront-nextjs-shadcn/app/auth/forgot-password/page.tsx +10 -5
  66. package/templates/storefront-nextjs-shadcn/app/blog/[slug]/loading.tsx +17 -0
  67. package/templates/storefront-nextjs-shadcn/app/blog/[slug]/page.tsx +43 -2
  68. package/templates/storefront-nextjs-shadcn/app/blog/loading.tsx +19 -0
  69. package/templates/storefront-nextjs-shadcn/app/brands/page.tsx +2 -1
  70. package/templates/storefront-nextjs-shadcn/app/cart/loading.tsx +26 -0
  71. package/templates/storefront-nextjs-shadcn/app/cart/page.tsx +6 -3
  72. package/templates/storefront-nextjs-shadcn/app/categories/[slug]/category-products-client.tsx +56 -0
  73. package/templates/storefront-nextjs-shadcn/app/categories/[slug]/loading.tsx +32 -0
  74. package/templates/storefront-nextjs-shadcn/app/categories/[slug]/page.tsx +76 -59
  75. package/templates/storefront-nextjs-shadcn/app/categories/page.tsx +8 -4
  76. package/templates/storefront-nextjs-shadcn/app/checkout/error.tsx +43 -0
  77. package/templates/storefront-nextjs-shadcn/app/checkout/loading.tsx +31 -0
  78. package/templates/storefront-nextjs-shadcn/app/checkout/page.tsx +116 -79
  79. package/templates/storefront-nextjs-shadcn/app/collections/[handle]/loading.tsx +19 -0
  80. package/templates/storefront-nextjs-shadcn/app/collections/[handle]/page.tsx +1 -1
  81. package/templates/storefront-nextjs-shadcn/app/collections/loading.tsx +18 -0
  82. package/templates/storefront-nextjs-shadcn/app/collections/page.tsx +7 -4
  83. package/templates/storefront-nextjs-shadcn/app/global-error.tsx +117 -0
  84. package/templates/storefront-nextjs-shadcn/app/globals.css +8 -0
  85. package/templates/storefront-nextjs-shadcn/app/layout.tsx +46 -11
  86. package/templates/storefront-nextjs-shadcn/app/products/[slug]/error.tsx +43 -0
  87. package/templates/storefront-nextjs-shadcn/app/products/[slug]/loading.tsx +29 -0
  88. package/templates/storefront-nextjs-shadcn/app/products/[slug]/page.tsx +6 -6
  89. package/templates/storefront-nextjs-shadcn/app/products/[slug]/product-client.tsx +15 -61
  90. package/templates/storefront-nextjs-shadcn/app/products/loading.tsx +32 -0
  91. package/templates/storefront-nextjs-shadcn/app/products/products-client.tsx +405 -151
  92. package/templates/storefront-nextjs-shadcn/app/search/loading.tsx +18 -0
  93. package/templates/storefront-nextjs-shadcn/app/wishlist/page.tsx +8 -5
  94. package/templates/storefront-nextjs-shadcn/codegen.ts +48 -31
  95. package/templates/storefront-nextjs-shadcn/components/account/customer-info.fragment.graphql +36 -0
  96. package/templates/storefront-nextjs-shadcn/components/account/order-details.tsx +3 -1
  97. package/templates/storefront-nextjs-shadcn/components/account/order-history.tsx +26 -24
  98. package/templates/storefront-nextjs-shadcn/components/account/order-summary.fragment.graphql +36 -0
  99. package/templates/storefront-nextjs-shadcn/components/auth/account-menu.tsx +9 -9
  100. package/templates/storefront-nextjs-shadcn/components/auth/login-form.tsx +11 -37
  101. package/templates/storefront-nextjs-shadcn/components/auth/register-form.tsx +37 -23
  102. package/templates/storefront-nextjs-shadcn/components/cart/cart-drawer.tsx +4 -3
  103. package/templates/storefront-nextjs-shadcn/components/cart/cart-icon.tsx +8 -5
  104. package/templates/storefront-nextjs-shadcn/components/cart/cart-item.tsx +1 -1
  105. package/templates/storefront-nextjs-shadcn/components/cart/cart-line.fragment.graphql +53 -0
  106. package/templates/storefront-nextjs-shadcn/components/cart/cart-summary.tsx +1 -1
  107. package/templates/storefront-nextjs-shadcn/components/cart/shipping-estimator.tsx +22 -7
  108. package/templates/storefront-nextjs-shadcn/components/commerce/currency-selector.tsx +2 -2
  109. package/templates/storefront-nextjs-shadcn/components/commerce/product-actions.tsx +1 -1
  110. package/templates/storefront-nextjs-shadcn/components/commerce/search-input.tsx +2 -2
  111. package/templates/storefront-nextjs-shadcn/components/common/price-display.tsx +35 -11
  112. package/templates/storefront-nextjs-shadcn/components/discount/discount-breakdown.tsx +1 -1
  113. package/templates/storefront-nextjs-shadcn/components/discount/discount-code-input.tsx +3 -3
  114. package/templates/storefront-nextjs-shadcn/components/filters/range-slider-filter.tsx +5 -5
  115. package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-input.tsx +2 -2
  116. package/templates/storefront-nextjs-shadcn/components/home/category-grid.tsx +2 -1
  117. package/templates/storefront-nextjs-shadcn/components/home/collection-card.fragment.graphql +21 -0
  118. package/templates/storefront-nextjs-shadcn/components/home/featured-collections.tsx +2 -12
  119. package/templates/storefront-nextjs-shadcn/components/home/index.ts +0 -1
  120. package/templates/storefront-nextjs-shadcn/components/hydrated.tsx +24 -0
  121. package/templates/storefront-nextjs-shadcn/components/layout/breadcrumbs.tsx +4 -4
  122. package/templates/storefront-nextjs-shadcn/components/layout/category-node.fragment.graphql +22 -0
  123. package/templates/storefront-nextjs-shadcn/components/layout/currency-selector.tsx +2 -2
  124. package/templates/storefront-nextjs-shadcn/components/layout/header.tsx +33 -23
  125. package/templates/storefront-nextjs-shadcn/components/loyalty/points-balance.tsx +2 -11
  126. package/templates/storefront-nextjs-shadcn/components/loyalty/points-history.tsx +8 -25
  127. package/templates/storefront-nextjs-shadcn/components/loyalty/referral-section.tsx +10 -19
  128. package/templates/storefront-nextjs-shadcn/components/loyalty/rewards-catalog.tsx +17 -41
  129. package/templates/storefront-nextjs-shadcn/components/loyalty/tier-progress.tsx +2 -29
  130. package/templates/storefront-nextjs-shadcn/components/order/index.ts +6 -1
  131. package/templates/storefront-nextjs-shadcn/components/product/b2b-price-display.tsx +3 -1
  132. package/templates/storefront-nextjs-shadcn/components/product/filter-active-pills.tsx +69 -0
  133. package/templates/storefront-nextjs-shadcn/components/product/filter-mobile-sheet.tsx +84 -0
  134. package/templates/storefront-nextjs-shadcn/components/product/filter-price-range.tsx +138 -0
  135. package/templates/storefront-nextjs-shadcn/components/product/index.ts +9 -2
  136. package/templates/storefront-nextjs-shadcn/components/product/product-card.fragment.graphql +49 -0
  137. package/templates/storefront-nextjs-shadcn/components/product/product-card.tsx +3 -31
  138. package/templates/storefront-nextjs-shadcn/components/product/product-detail.fragment.graphql +52 -0
  139. package/templates/storefront-nextjs-shadcn/components/product/product-filters.tsx +176 -123
  140. package/templates/storefront-nextjs-shadcn/components/product/product-grid.tsx +3 -5
  141. package/templates/storefront-nextjs-shadcn/components/product/product-image.tsx +2 -2
  142. package/templates/storefront-nextjs-shadcn/components/product/product-price.tsx +2 -2
  143. package/templates/storefront-nextjs-shadcn/components/product/product-reviews.tsx +5 -4
  144. package/templates/storefront-nextjs-shadcn/components/product/product-sort.tsx +19 -7
  145. package/templates/storefront-nextjs-shadcn/components/product/product-variant-selector.tsx +8 -23
  146. package/templates/storefront-nextjs-shadcn/components/product/product-variant.fragment.graphql +51 -0
  147. package/templates/storefront-nextjs-shadcn/components/product/review-card.tsx +1 -1
  148. package/templates/storefront-nextjs-shadcn/components/product/review-form.tsx +1 -7
  149. package/templates/storefront-nextjs-shadcn/components/product/savings-display.tsx +17 -2
  150. package/templates/storefront-nextjs-shadcn/components/product/similar-products.tsx +3 -2
  151. package/templates/storefront-nextjs-shadcn/components/providers/index.ts +1 -1
  152. package/templates/storefront-nextjs-shadcn/components/providers/stores-provider.tsx +30 -0
  153. package/templates/storefront-nextjs-shadcn/components/providers/theme-provider.tsx +1 -1
  154. package/templates/storefront-nextjs-shadcn/components/returns/index.ts +2 -2
  155. package/templates/storefront-nextjs-shadcn/components/returns/return-request-form.tsx +3 -2
  156. package/templates/storefront-nextjs-shadcn/components/search/search-results.tsx +3 -2
  157. package/templates/storefront-nextjs-shadcn/components/ui/form.tsx +174 -0
  158. package/templates/storefront-nextjs-shadcn/components/ui/index.ts +30 -2
  159. package/templates/storefront-nextjs-shadcn/components/ui/progress.tsx +40 -0
  160. package/templates/storefront-nextjs-shadcn/components/ui/sheet.tsx +107 -0
  161. package/templates/storefront-nextjs-shadcn/components/ui/slider.tsx +33 -0
  162. package/templates/storefront-nextjs-shadcn/components/ui/textarea.tsx +24 -0
  163. package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-icon.tsx +3 -1
  164. package/templates/storefront-nextjs-shadcn/generated/graphql.ts +12779 -0
  165. package/templates/storefront-nextjs-shadcn/graphql/custom.example.graphql +159 -0
  166. package/templates/storefront-nextjs-shadcn/hooks/index.ts +2 -0
  167. package/templates/storefront-nextjs-shadcn/hooks/use-auth-sync.ts +42 -0
  168. package/templates/storefront-nextjs-shadcn/hooks/use-auth.ts +17 -295
  169. package/templates/storefront-nextjs-shadcn/hooks/use-cart-actions.ts +51 -19
  170. package/templates/storefront-nextjs-shadcn/hooks/use-cart-sync.ts +13 -9
  171. package/templates/storefront-nextjs-shadcn/lib/auth/routes.ts +4 -17
  172. package/templates/storefront-nextjs-shadcn/lib/graphql/client.ts +22 -99
  173. package/templates/storefront-nextjs-shadcn/lib/graphql/config.ts +32 -0
  174. package/templates/storefront-nextjs-shadcn/lib/graphql/fragments.ts +34 -0
  175. package/templates/storefront-nextjs-shadcn/lib/graphql/hooks.ts +687 -632
  176. package/templates/storefront-nextjs-shadcn/lib/graphql/query-keys.ts +86 -0
  177. package/templates/storefront-nextjs-shadcn/lib/graphql/server.ts +131 -182
  178. package/templates/storefront-nextjs-shadcn/lib/graphql/types.ts +62 -0
  179. package/templates/storefront-nextjs-shadcn/lib/theme/theme-config.ts +0 -17
  180. package/templates/storefront-nextjs-shadcn/next-env.d.ts +6 -0
  181. package/templates/storefront-nextjs-shadcn/package.dev.json +1 -3
  182. package/templates/storefront-nextjs-shadcn/package.json +12 -13
  183. package/templates/storefront-nextjs-shadcn/package.json.template +6 -7
  184. package/templates/storefront-nextjs-shadcn/proxy.ts +3 -4
  185. package/templates/storefront-nextjs-shadcn/stores/cart-store.ts +41 -39
  186. package/templates/storefront-nextjs-shadcn/stores/checkout-store.ts +64 -75
  187. package/templates/storefront-nextjs-shadcn/stores/wishlist-store.ts +178 -177
  188. package/templates/storefront-nextjs-shadcn/tsconfig.json +23 -5
  189. package/templates/storefront-nextjs-shadcn/CART_INTEGRATION.md +0 -282
  190. package/templates/storefront-nextjs-shadcn/GRAPHQL_DOCUMENT_NAMES.md +0 -190
  191. package/templates/storefront-nextjs-shadcn/GRAPHQL_ERROR_HANDLING.md +0 -263
  192. package/templates/storefront-nextjs-shadcn/GRAPHQL_FIXES_SUMMARY.md +0 -135
  193. package/templates/storefront-nextjs-shadcn/GRAPHQL_INTEGRATION_COMPLETE.md +0 -142
  194. package/templates/storefront-nextjs-shadcn/INTEGRATION_CHECKLIST.md +0 -448
  195. package/templates/storefront-nextjs-shadcn/PRODUCT_DETAIL_PAGE_IMPLEMENTATION.md +0 -307
  196. package/templates/storefront-nextjs-shadcn/THEME_CUSTOMIZATION.md +0 -245
  197. package/templates/storefront-nextjs-shadcn/components/providers/currency-provider.tsx +0 -103
  198. package/templates/storefront-nextjs-shadcn/graphql/collections.example.ts +0 -168
  199. package/templates/storefront-nextjs-shadcn/graphql/products.example.ts +0 -160
  200. package/templates/storefront-nextjs-shadcn/lib/auth/cookies.ts +0 -220
  201. package/templates/storefront-nextjs-shadcn/lib/config.ts +0 -46
  202. package/templates/storefront-nextjs-shadcn/lib/currency/IMPLEMENTATION_SUMMARY.md +0 -254
  203. package/templates/storefront-nextjs-shadcn/lib/currency/README.md +0 -464
  204. package/templates/storefront-nextjs-shadcn/lib/currency/cookie-manager.test.ts +0 -328
  205. package/templates/storefront-nextjs-shadcn/lib/currency/cookie-manager.ts +0 -295
  206. package/templates/storefront-nextjs-shadcn/lib/currency/index.ts +0 -27
  207. package/templates/storefront-nextjs-shadcn/lib/format.ts +0 -226
  208. package/templates/storefront-nextjs-shadcn/lib/hooks.ts +0 -30
  209. package/templates/storefront-nextjs-shadcn/stores/auth-store.ts +0 -66
  210. package/templates/storefront-nextjs-shadcn/stores/currency-store.ts +0 -103
@@ -1,17 +1,17 @@
1
1
  "use client";
2
2
 
3
- import { useState } from "react";
3
+ import { useState, useCallback } from "react";
4
+ import Link from "next/link";
4
5
  import { Plus } from "lucide-react";
5
- import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
6
- import { getGraphQLClient } from "@/lib/graphql/client";
7
- import { useAuthStore } from "@/stores/auth-store";
6
+ import { useAuthStore, useAuthHydrated } from "@doswiftly/storefront-sdk/react";
8
7
  import {
9
- CustomerDocument,
10
- CustomerAddressCreateDocument,
11
- CustomerAddressUpdateDocument,
12
- CustomerAddressDeleteDocument,
13
- CustomerDefaultAddressUpdateDocument,
14
- } from "@/generated/graphql";
8
+ useCustomer,
9
+ useCustomerAddressCreate,
10
+ useCustomerAddressUpdate,
11
+ useCustomerAddressDelete,
12
+ useCustomerDefaultAddressUpdate,
13
+ } from "@/lib/graphql/hooks";
14
+ import { useHydrated } from "@doswiftly/storefront-sdk/react";
15
15
  import { Button } from "@/components/ui/button";
16
16
  import { Breadcrumbs } from "@/components/layout/breadcrumbs";
17
17
  import {
@@ -30,94 +30,67 @@ import {
30
30
  } from "@/components/ui/dialog";
31
31
 
32
32
  export default function AddressesPage() {
33
- const client = getGraphQLClient();
34
- const queryClient = useQueryClient();
35
- const { accessToken } = useAuthStore();
36
-
37
- // Fetch customer with addresses
38
- const { data: customerData, isLoading } = useQuery({
39
- queryKey: ["customer", "addresses", accessToken],
40
- queryFn: async () => {
41
- if (!accessToken) return null;
42
- const result = await client.request(CustomerDocument, {
43
- customerAccessToken: accessToken,
44
- });
45
- return result.customer;
46
- },
47
- enabled: !!accessToken,
48
- });
33
+ const hydrated = useHydrated();
34
+ const authHydrated = useAuthHydrated();
35
+ const accessToken = useAuthStore((s) => s.accessToken);
36
+
37
+ const { data: customerData, isPending } = useCustomer();
49
38
 
50
39
  const [isFormOpen, setIsFormOpen] = useState(false);
51
40
  const [editingAddress, setEditingAddress] = useState<Address | undefined>();
41
+ const [mutationError, setMutationError] = useState<string | null>(null);
52
42
 
53
- // Delete address mutation
54
- const deleteMutation = useMutation({
55
- mutationFn: async (id: string) => {
56
- return client.request(CustomerAddressDeleteDocument, {
57
- id,
58
- customerAccessToken: accessToken!,
59
- });
60
- },
61
- onSuccess: () => {
62
- queryClient.invalidateQueries({ queryKey: ["customer", "addresses"] });
63
- },
43
+ const checkUserErrors = useCallback(<T extends Record<string, unknown>>(data: T, operationKey: keyof T) => {
44
+ const payload = data?.[operationKey] as { userErrors?: Array<{ message: string }> } | undefined;
45
+ if (payload?.userErrors?.length) {
46
+ setMutationError(payload.userErrors[0].message);
47
+ return true;
48
+ }
49
+ setMutationError(null);
50
+ return false;
51
+ }, []);
52
+
53
+ const deleteMutation = useCustomerAddressDelete({
54
+ onSuccess: (data) => checkUserErrors(data, 'customerAddressDelete'),
55
+ onError: () => setMutationError('Failed to delete address'),
56
+ });
57
+ const setDefaultMutation = useCustomerDefaultAddressUpdate({
58
+ onSuccess: (data) => checkUserErrors(data, 'customerDefaultAddressUpdate'),
59
+ onError: () => setMutationError('Failed to set default address'),
64
60
  });
65
61
 
66
- // Set default address mutation
67
- const setDefaultMutation = useMutation({
68
- mutationFn: async (addressId: string) => {
69
- return client.request(CustomerDefaultAddressUpdateDocument, {
70
- addressId,
71
- customerAccessToken: accessToken!,
72
- });
73
- },
74
- onSuccess: () => {
75
- queryClient.invalidateQueries({ queryKey: ["customer", "addresses"] });
62
+ const createMutation = useCustomerAddressCreate({
63
+ onSuccess: (data) => {
64
+ if (!checkUserErrors(data, 'customerAddressCreate')) {
65
+ setIsFormOpen(false);
66
+ }
76
67
  },
68
+ onError: () => setMutationError('Failed to create address'),
77
69
  });
78
70
 
79
- // Create/Update address mutation
80
- const saveMutation = useMutation({
81
- mutationFn: async ({
82
- id,
83
- address,
84
- }: {
85
- id?: string;
86
- address: any;
87
- }) => {
88
- if (id) {
89
- return client.request(CustomerAddressUpdateDocument, {
90
- id,
91
- address,
92
- customerAccessToken: accessToken!,
93
- });
94
- } else {
95
- return client.request(CustomerAddressCreateDocument, {
96
- address,
97
- customerAccessToken: accessToken!,
98
- });
71
+ const updateMutation = useCustomerAddressUpdate({
72
+ onSuccess: (data) => {
73
+ if (!checkUserErrors(data, 'customerAddressUpdate')) {
74
+ setIsFormOpen(false);
99
75
  }
100
76
  },
101
- onSuccess: () => {
102
- queryClient.invalidateQueries({ queryKey: ["customer", "addresses"] });
103
- setIsFormOpen(false);
104
- },
77
+ onError: () => setMutationError('Failed to update address'),
105
78
  });
106
79
 
107
- const addresses = customerData?.addresses?.edges?.map((edge) => ({
108
- id: edge.node.id,
109
- firstName: edge.node.firstName || "",
110
- lastName: edge.node.lastName || "",
111
- company: edge.node.company || "",
112
- address1: edge.node.address1 || "",
113
- address2: edge.node.address2 || "",
114
- city: edge.node.city || "",
115
- province: edge.node.province || "",
116
- zip: edge.node.zip || "",
117
- country: edge.node.country || "",
118
- phone: edge.node.phone || "",
119
- isDefault: edge.node.id === customerData?.defaultAddress?.id,
120
- })) || [];
80
+ const addresses = (customerData?.customer?.addresses ?? []).map((addr) => ({
81
+ id: addr.id,
82
+ firstName: addr.firstName || "",
83
+ lastName: addr.lastName || "",
84
+ company: addr.company || "",
85
+ address1: addr.address1 || "",
86
+ address2: addr.address2 || "",
87
+ city: addr.city || "",
88
+ province: addr.province || "",
89
+ zip: addr.zip || "",
90
+ country: addr.country || "",
91
+ phone: addr.phone || "",
92
+ isDefault: addr.isDefault,
93
+ }));
121
94
 
122
95
  const handleAdd = () => {
123
96
  setEditingAddress(undefined);
@@ -153,13 +126,15 @@ export default function AddressesPage() {
153
126
  phone: data.phone || null,
154
127
  };
155
128
 
156
- await saveMutation.mutateAsync({
157
- id: editingAddress?.id,
158
- address,
159
- });
129
+ if (editingAddress?.id) {
130
+ await updateMutation.mutateAsync({ id: editingAddress.id, address });
131
+ } else {
132
+ await createMutation.mutateAsync({ address });
133
+ }
160
134
  };
161
135
 
162
- if (isLoading) {
136
+ // Wait for DOM + auth persist hydration before checking accessToken
137
+ if (!hydrated || !authHydrated) {
163
138
  return (
164
139
  <div className="container mx-auto px-4 py-8">
165
140
  <div className="animate-pulse space-y-4">
@@ -170,6 +145,33 @@ export default function AddressesPage() {
170
145
  );
171
146
  }
172
147
 
148
+ // Data loading (customer query fetching after accessToken is available)
149
+ if (isPending) {
150
+ return (
151
+ <div className="container mx-auto px-4 py-8">
152
+ <div className="animate-pulse space-y-4">
153
+ <div className="h-8 bg-muted rounded w-1/4"></div>
154
+ <div className="h-64 bg-muted rounded"></div>
155
+ </div>
156
+ </div>
157
+ );
158
+ }
159
+
160
+ if (!accessToken) {
161
+ return (
162
+ <div className="container mx-auto px-4 py-8">
163
+ <Breadcrumbs className="mb-6" />
164
+ <div className="rounded-lg border border-destructive bg-destructive/10 p-8 text-center text-sm text-destructive">
165
+ You need to{" "}
166
+ <Link href="/auth/login?redirect=/account/addresses" className="font-medium underline">
167
+ sign in
168
+ </Link>{" "}
169
+ to manage your addresses.
170
+ </div>
171
+ </div>
172
+ );
173
+ }
174
+
173
175
  return (
174
176
  <div className="container mx-auto px-4 py-8">
175
177
  <Breadcrumbs className="mb-6" />
@@ -187,6 +189,12 @@ export default function AddressesPage() {
187
189
  </Button>
188
190
  </div>
189
191
 
192
+ {mutationError && (
193
+ <div className="mb-4 rounded-lg border border-destructive bg-destructive/10 p-4 text-sm text-destructive">
194
+ {mutationError}
195
+ </div>
196
+ )}
197
+
190
198
  <AddressList
191
199
  addresses={addresses}
192
200
  onEdit={handleEdit}
@@ -195,7 +203,6 @@ export default function AddressesPage() {
195
203
  onAdd={handleAdd}
196
204
  />
197
205
 
198
- {/* Address Form Dialog */}
199
206
  <Dialog open={isFormOpen} onOpenChange={setIsFormOpen}>
200
207
  <DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
201
208
  <DialogHeader>
@@ -0,0 +1,43 @@
1
+ "use client";
2
+
3
+ import { useEffect } from "react";
4
+ import Link from "next/link";
5
+ import { AlertCircle } from "lucide-react";
6
+ import { Button } from "@/components/ui/button";
7
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
8
+
9
+ export default function AccountError({
10
+ error,
11
+ reset,
12
+ }: {
13
+ error: Error & { digest?: string };
14
+ reset: () => void;
15
+ }) {
16
+ useEffect(() => {
17
+ console.error("[Account Error]", error);
18
+ }, [error]);
19
+
20
+ return (
21
+ <div className="container mx-auto flex min-h-[400px] items-center justify-center px-4 py-16">
22
+ <Card className="max-w-md">
23
+ <CardHeader>
24
+ <CardTitle className="flex items-center gap-2 text-destructive">
25
+ <AlertCircle className="h-5 w-5" />
26
+ Something went wrong
27
+ </CardTitle>
28
+ </CardHeader>
29
+ <CardContent className="space-y-4">
30
+ <p className="text-sm text-muted-foreground">
31
+ We couldn&apos;t load your account data. This might be a temporary issue.
32
+ </p>
33
+ <div className="flex gap-3">
34
+ <Button onClick={reset}>Try again</Button>
35
+ <Button variant="outline" asChild>
36
+ <Link href="/">Go to homepage</Link>
37
+ </Button>
38
+ </div>
39
+ </CardContent>
40
+ </Card>
41
+ </div>
42
+ );
43
+ }
@@ -0,0 +1,19 @@
1
+ import { Skeleton } from "@/components/ui/skeleton";
2
+
3
+ export default function AccountLoading() {
4
+ return (
5
+ <div className="container mx-auto px-4 py-16">
6
+ <div className="mx-auto max-w-4xl">
7
+ <div className="mb-8">
8
+ <Skeleton className="mb-2 h-10 w-48" />
9
+ <Skeleton className="h-5 w-80" />
10
+ </div>
11
+ <div className="grid gap-6 md:grid-cols-2">
12
+ {[...Array(4)].map((_, i) => (
13
+ <Skeleton key={i} className="h-48 w-full rounded-lg" />
14
+ ))}
15
+ </div>
16
+ </div>
17
+ </div>
18
+ );
19
+ }
@@ -14,7 +14,7 @@
14
14
  * Requirements: R7, R8, R10.4
15
15
  */
16
16
 
17
- import { useState, useEffect } from 'react';
17
+ import { useState } from 'react';
18
18
  import Link from 'next/link';
19
19
  import { ArrowLeft, Gift, Award, History, Loader2, Users, AlertCircle } from 'lucide-react';
20
20
  import { Button } from '@/components/ui/button';
@@ -27,100 +27,7 @@ import { TierProgress } from '@/components/loyalty/tier-progress';
27
27
  import { RewardsCatalog } from '@/components/loyalty/rewards-catalog';
28
28
  import { PointsHistory } from '@/components/loyalty/points-history';
29
29
  import { ReferralSection } from '@/components/loyalty/referral-section';
30
- import { useAuth } from '@doswiftly/commerce-sdk/graphql/react';
31
-
32
- // Type imports - these match the GraphQL schema
33
- type TierType = 'BRONZE' | 'SILVER' | 'GOLD' | 'PLATINUM' | 'DIAMOND';
34
-
35
- interface CustomBenefit {
36
- name: string;
37
- description?: string | null;
38
- icon?: string | null;
39
- }
40
-
41
- interface LoyaltyTier {
42
- id: string;
43
- name: string;
44
- type?: TierType | null;
45
- minPoints: number;
46
- minAnnualSpend?: { amount: string; currencyCode: string } | null;
47
- pointsMultiplier: number;
48
- customBenefits: CustomBenefit[];
49
- }
50
-
51
- interface PointsSummary {
52
- totalPoints: number;
53
- currentPoints: number;
54
- pendingPoints: number;
55
- redeemedPoints: number;
56
- expiredPoints: number;
57
- expiringPoints?: number;
58
- nextExpiryDate?: string;
59
- }
60
-
61
- interface TierProgressData {
62
- currentTier: LoyaltyTier;
63
- nextTier?: LoyaltyTier;
64
- pointsToNextTier: number;
65
- progressPercent: number;
66
- spendToNextTier?: { amount: string; currencyCode: string };
67
- }
68
-
69
- interface LoyaltyMember {
70
- id: string;
71
- customerId: string;
72
- points: PointsSummary;
73
- tier?: LoyaltyTier;
74
- tierProgress?: TierProgressData;
75
- annualSpend: { amount: string; currencyCode: string };
76
- lastActivityAt?: string;
77
- enrolledAt: string;
78
- }
79
-
80
- interface LoyaltyReward {
81
- id: string;
82
- name: string;
83
- slug: string;
84
- type: string;
85
- pointsCost: number;
86
- discountPercent?: number | null;
87
- discountAmount?: { amount: string; currencyCode: string } | null;
88
- description?: string | null;
89
- image?: { url: string; altText?: string | null } | null;
90
- available: boolean;
91
- tierRequired?: LoyaltyTier | null;
92
- remainingRedemptions?: number | null;
93
- }
94
-
95
- interface LoyaltyTransaction {
96
- id: string;
97
- type: string;
98
- points: number;
99
- balanceAfter: number;
100
- orderId?: string;
101
- description?: string;
102
- expiresAt?: string;
103
- createdAt: string;
104
- }
105
-
106
- interface LoyaltySettings {
107
- enabled: boolean;
108
- pointsName: string;
109
- pointsPerCurrency: number;
110
- pointsExpiryMonths?: number;
111
- referralEnabled: boolean;
112
- referralPoints?: number;
113
- referralBonusPoints?: number;
114
- }
115
-
116
- interface ReferralStats {
117
- referralCode: string;
118
- shareUrl: string;
119
- totalReferred: number;
120
- completedReferrals: number;
121
- pendingReferrals: number;
122
- totalPointsEarned: number;
123
- }
30
+ import { useAuthStore, useAuthHydrated } from '@doswiftly/storefront-sdk/react';
124
31
 
125
32
  // Real hooks from generated GraphQL
126
33
  import {
@@ -134,20 +41,23 @@ import {
134
41
 
135
42
  export default function LoyaltyPage() {
136
43
  const [activeTab, setActiveTab] = useState('overview');
137
- const { isAuthenticated } = useAuth();
44
+ const { isAuthenticated } = useAuthStore();
45
+ const isHydrated = useAuthHydrated();
138
46
 
139
47
  // Fetch loyalty data using real GraphQL hooks
140
- const { data: memberData, isLoading: memberLoading } = useLoyaltyMember();
141
- const { data: rewardsData, isLoading: rewardsLoading } = useLoyaltyRewards();
142
- const { data: transactionsData } = useLoyaltyTransactions({ first: 20 });
48
+ // Settings don't require auth always fetch
143
49
  const { data: settingsData } = useLoyaltySettings();
50
+ // All other queries require auth — hooks internally gate with `enabled: !!accessToken`
51
+ const { data: memberData, isLoading: memberLoading, error: memberError } = useLoyaltyMember();
52
+ const { data: rewardsData, isLoading: rewardsLoading, error: rewardsError } = useLoyaltyRewards();
53
+ const { data: transactionsData } = useLoyaltyTransactions({ first: 20 });
144
54
  const { data: referralData } = useReferralStats();
145
55
  const redeemMutation = useRedeemLoyaltyReward();
146
56
 
147
57
  // Extract data from query responses
148
58
  const member = memberData?.loyaltyMember;
149
59
  const rewards = rewardsData?.loyaltyRewards ?? [];
150
- const transactions = transactionsData?.loyaltyTransactions?.edges?.map((e: any) => e.node) ?? [];
60
+ const transactions = transactionsData?.loyaltyTransactions?.edges?.map((e) => e.node) ?? [];
151
61
  const settings = settingsData?.loyaltySettings;
152
62
  const referralStats = referralData?.referralStats;
153
63
 
@@ -164,7 +74,7 @@ export default function LoyaltyPage() {
164
74
  const payload = result.redeemLoyaltyReward;
165
75
 
166
76
  if (payload?.userErrors?.length) {
167
- throw new Error(payload.userErrors[0]);
77
+ throw new Error(payload.userErrors[0] || 'Unknown error');
168
78
  }
169
79
 
170
80
  if (payload?.success) {
@@ -182,6 +92,17 @@ export default function LoyaltyPage() {
182
92
  }
183
93
  };
184
94
 
95
+ // Show skeleton while Zustand persist is hydrating from localStorage
96
+ if (!isHydrated) {
97
+ return (
98
+ <div className="container max-w-6xl py-8">
99
+ <div className="flex items-center justify-center min-h-[400px]">
100
+ <Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
101
+ </div>
102
+ </div>
103
+ );
104
+ }
105
+
185
106
  // Show login prompt if not authenticated
186
107
  if (!isAuthenticated) {
187
108
  return (
@@ -224,6 +145,27 @@ export default function LoyaltyPage() {
224
145
  );
225
146
  }
226
147
 
148
+ // Show error state if primary queries failed
149
+ if (memberError || rewardsError) {
150
+ return (
151
+ <div className="container max-w-6xl py-8">
152
+ <Link href="/account">
153
+ <Button variant="ghost" size="sm" className="mb-4">
154
+ <ArrowLeft className="h-4 w-4 mr-2" />
155
+ Powrót do konta
156
+ </Button>
157
+ </Link>
158
+
159
+ <Alert variant="destructive">
160
+ <AlertCircle className="h-4 w-4" />
161
+ <AlertDescription>
162
+ Nie udało się załadować danych programu lojalnościowego. Spróbuj ponownie później.
163
+ </AlertDescription>
164
+ </Alert>
165
+ </div>
166
+ );
167
+ }
168
+
227
169
  // Show disabled program message
228
170
  if (programDisabled) {
229
171
  return (
@@ -277,43 +219,6 @@ export default function LoyaltyPage() {
277
219
  );
278
220
  }
279
221
 
280
- // Map tier data for components
281
- const tierData = member.tier
282
- ? {
283
- id: member.tier.id,
284
- name: member.tier.name,
285
- type: member.tier.type,
286
- minPoints: member.tier.minPoints,
287
- pointsMultiplier: member.tier.pointsMultiplier,
288
- customBenefits: member.tier.customBenefits ?? [],
289
- }
290
- : null;
291
-
292
- const progressData = member.tierProgress
293
- ? {
294
- currentTier: {
295
- id: member.tierProgress.currentTier.id,
296
- name: member.tierProgress.currentTier.name,
297
- type: member.tierProgress.currentTier.type,
298
- minPoints: member.tierProgress.currentTier.minPoints,
299
- pointsMultiplier: member.tierProgress.currentTier.pointsMultiplier,
300
- customBenefits: member.tierProgress.currentTier.customBenefits ?? [],
301
- },
302
- nextTier: member.tierProgress.nextTier
303
- ? {
304
- id: member.tierProgress.nextTier.id,
305
- name: member.tierProgress.nextTier.name,
306
- type: member.tierProgress.nextTier.type,
307
- minPoints: member.tierProgress.nextTier.minPoints,
308
- pointsMultiplier: member.tierProgress.nextTier.pointsMultiplier,
309
- customBenefits: member.tierProgress.nextTier.customBenefits ?? [],
310
- }
311
- : undefined,
312
- pointsToNextTier: member.tierProgress.pointsToNextTier,
313
- progressPercent: member.tierProgress.progressPercent,
314
- }
315
- : null;
316
-
317
222
  // Check if referral tab should be shown
318
223
  const showReferralTab = settings?.referralEnabled && referralStats;
319
224
 
@@ -335,7 +240,7 @@ export default function LoyaltyPage() {
335
240
  Zbieraj {settings?.pointsName ?? 'punkty'} i wymieniaj je na nagrody
336
241
  </p>
337
242
  </div>
338
- {tierData && <TierBadge tier={tierData.type} name={tierData.name} size="lg" />}
243
+ {member.tier && <TierBadge tier={member.tier.type} name={member.tier.name} size="lg" />}
339
244
  </div>
340
245
  </div>
341
246
 
@@ -366,22 +271,22 @@ export default function LoyaltyPage() {
366
271
  <TabsContent value="overview" className="space-y-6">
367
272
  <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
368
273
  <PointsBalance points={member.points} />
369
- {progressData && <TierProgress progress={progressData} />}
274
+ {member.tierProgress && <TierProgress progress={member.tierProgress} />}
370
275
  </div>
371
276
 
372
277
  {/* Tier Benefits */}
373
- {tierData && (
278
+ {member.tier && (
374
279
  <div className="p-6 bg-gradient-to-r from-amber-50 to-yellow-50 dark:from-amber-950/30 dark:to-yellow-950/30 rounded-lg border border-amber-200 dark:border-amber-800">
375
280
  <h3 className="font-semibold mb-4 flex items-center gap-2">
376
281
  <Award className="h-5 w-5 text-amber-600" />
377
- Twoje korzyści na poziomie {tierData.name}
282
+ Twoje korzyści na poziomie {member.tier.name}
378
283
  </h3>
379
284
  <div className="grid grid-cols-1 sm:grid-cols-3 gap-4 text-sm">
380
285
  <div className="p-3 bg-white/50 dark:bg-black/20 rounded-lg">
381
286
  <div className="font-medium">Mnożnik punktów</div>
382
- <div className="text-2xl font-bold text-amber-600">x{tierData.pointsMultiplier}</div>
287
+ <div className="text-2xl font-bold text-amber-600">x{member.tier.pointsMultiplier}</div>
383
288
  </div>
384
- {tierData.customBenefits.slice(0, 2).map((benefit, idx) => (
289
+ {member.tier.customBenefits?.slice(0, 2).map((benefit, idx) => (
385
290
  <div key={idx} className="p-3 bg-white/50 dark:bg-black/20 rounded-lg">
386
291
  <div className="font-medium">{benefit.name}</div>
387
292
  {benefit.description && (
@@ -390,9 +295,9 @@ export default function LoyaltyPage() {
390
295
  </div>
391
296
  ))}
392
297
  </div>
393
- {tierData.customBenefits.length > 2 && (
298
+ {(member.tier.customBenefits?.length ?? 0) > 2 && (
394
299
  <div className="mt-4 flex flex-wrap gap-2">
395
- {tierData.customBenefits.slice(2).map((benefit, idx) => (
300
+ {member.tier.customBenefits?.slice(2).map((benefit, idx) => (
396
301
  <div key={idx} className="px-3 py-1.5 bg-white/50 dark:bg-black/20 rounded-full text-sm">
397
302
  {benefit.name}
398
303
  </div>
@@ -430,14 +335,7 @@ export default function LoyaltyPage() {
430
335
  </p>
431
336
  </div>
432
337
  <RewardsCatalog
433
- rewards={rewards.map((r) => ({
434
- ...r,
435
- discountAmount: r.discountAmount
436
- ? { amount: r.discountAmount.amount, currencyCode: r.discountAmount.currencyCode }
437
- : undefined,
438
- // Map tierRequired from LoyaltyTier object to just the type string
439
- tierRequired: r.tierRequired?.type,
440
- }))}
338
+ rewards={rewards}
441
339
  currentPoints={member.points.currentPoints}
442
340
  currentTier={member.tier?.type}
443
341
  onRedeem={handleRedeemReward}
@@ -464,14 +362,7 @@ export default function LoyaltyPage() {
464
362
  </p>
465
363
  </div>
466
364
  <ReferralSection
467
- referralCode={referralStats.referralCode}
468
- shareUrl={referralStats.shareUrl}
469
- stats={{
470
- totalReferred: referralStats.totalReferred,
471
- completedReferrals: referralStats.completedReferrals,
472
- pendingReferrals: referralStats.pendingReferrals,
473
- totalPointsEarned: referralStats.totalPointsEarned,
474
- }}
365
+ referralStats={referralStats}
475
366
  pointsName={settings?.pointsName ?? 'punktów'}
476
367
  referralPoints={settings?.referralPoints ?? 0}
477
368
  bonusPoints={settings?.referralBonusPoints ?? 0}