@doswiftly/cli 0.2.6 → 1.0.0

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 (30) hide show
  1. package/CHANGELOG.md +639 -0
  2. package/package.json +1 -1
  3. package/templates/storefront-nextjs-shadcn/app/[locale]/account/addresses/page.tsx +10 -10
  4. package/templates/storefront-nextjs-shadcn/app/[locale]/account/orders/[id]/page.tsx +9 -9
  5. package/templates/storefront-nextjs-shadcn/app/[locale]/account/orders/[id]/tracking/page.tsx +1 -1
  6. package/templates/storefront-nextjs-shadcn/app/[locale]/account/orders/page.tsx +2 -2
  7. package/templates/storefront-nextjs-shadcn/app/[locale]/account/settings/page.tsx +3 -3
  8. package/templates/storefront-nextjs-shadcn/app/[locale]/checkout/page.tsx +41 -41
  9. package/templates/storefront-nextjs-shadcn/app/[locale]/products/[slug]/product-client.tsx +1 -1
  10. package/templates/storefront-nextjs-shadcn/app/api/auth/whoami/route.ts +6 -0
  11. package/templates/storefront-nextjs-shadcn/components/account/address-form.tsx +43 -43
  12. package/templates/storefront-nextjs-shadcn/components/account/address-list.tsx +9 -9
  13. package/templates/storefront-nextjs-shadcn/components/account/customer-info.fragment.graphql +5 -5
  14. package/templates/storefront-nextjs-shadcn/components/account/order-details.tsx +8 -8
  15. package/templates/storefront-nextjs-shadcn/components/account/order-history.tsx +2 -2
  16. package/templates/storefront-nextjs-shadcn/components/account/order-summary.fragment.graphql +15 -13
  17. package/templates/storefront-nextjs-shadcn/components/cart/cart-line.fragment.graphql +6 -6
  18. package/templates/storefront-nextjs-shadcn/components/discount/discount-code-input.tsx +10 -10
  19. package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-input.tsx +9 -9
  20. package/templates/storefront-nextjs-shadcn/components/order/delivery-estimate.tsx +2 -2
  21. package/templates/storefront-nextjs-shadcn/components/product/product-card.tsx +1 -1
  22. package/templates/storefront-nextjs-shadcn/components/seo/product-json-ld.ts +1 -1
  23. package/templates/storefront-nextjs-shadcn/hooks/use-auth-sync.ts +43 -25
  24. package/templates/storefront-nextjs-shadcn/hooks/use-cart-actions.ts +1 -1
  25. package/templates/storefront-nextjs-shadcn/hooks/use-cart-sync.ts +14 -14
  26. package/templates/storefront-nextjs-shadcn/lib/graphql/hooks.ts +24 -35
  27. package/templates/storefront-nextjs-shadcn/lib/graphql/server.ts +21 -4
  28. package/templates/storefront-nextjs-shadcn/messages/en.json +6 -6
  29. package/templates/storefront-nextjs-shadcn/messages/pl.json +6 -6
  30. package/templates/storefront-nextjs-shadcn/stores/checkout-store.ts +8 -8
@@ -18,11 +18,11 @@ fragment CartLineFields on CartLine {
18
18
  productTitle
19
19
  productHandle
20
20
  productType
21
- merchandise {
21
+ variant {
22
22
  id
23
23
  title
24
- available
25
- quantityAvailable
24
+ isAvailable
25
+ availableStock
26
26
  price {
27
27
  amount
28
28
  currencyCode
@@ -38,15 +38,15 @@ fragment CartLineFields on CartLine {
38
38
  }
39
39
  }
40
40
  cost {
41
- amountPerQuantity {
41
+ pricePerUnit {
42
42
  amount
43
43
  currencyCode
44
44
  }
45
- totalAmount {
45
+ total {
46
46
  amount
47
47
  currencyCode
48
48
  }
49
- compareAtAmountPerQuantity {
49
+ compareAtPricePerUnit {
50
50
  amount
51
51
  currencyCode
52
52
  }
@@ -22,7 +22,7 @@ import { Alert, AlertDescription } from "@/components/ui/alert";
22
22
  import { formatPrice } from "@doswiftly/storefront-sdk";
23
23
 
24
24
  export interface DiscountValidationResult {
25
- valid: boolean;
25
+ isValid: boolean;
26
26
  code?: string;
27
27
  discountType?: "PERCENTAGE" | "FIXED_AMOUNT" | "FREE_SHIPPING";
28
28
  discountValue?: number;
@@ -101,7 +101,7 @@ export function DiscountCodeInput({
101
101
  const result = await onValidate(trimmedCode);
102
102
  setValidationResult(result);
103
103
 
104
- if (!result.valid) {
104
+ if (!result.isValid) {
105
105
  setError(result.errorMessage || t("invalidCode"));
106
106
  }
107
107
  } catch (e: unknown) {
@@ -111,7 +111,7 @@ export function DiscountCodeInput({
111
111
  }
112
112
  } else {
113
113
  // No validation function, assume valid
114
- setValidationResult({ valid: true, code: trimmedCode });
114
+ setValidationResult({ isValid: true, code: trimmedCode });
115
115
  }
116
116
  }, [code, appliedCodes, onValidate]);
117
117
 
@@ -119,12 +119,12 @@ export function DiscountCodeInput({
119
119
  const trimmedCode = code.trim().toUpperCase();
120
120
 
121
121
  // If not yet validated, validate first
122
- if (!validationResult?.valid) {
122
+ if (!validationResult?.isValid) {
123
123
  await handleValidate();
124
124
  return;
125
125
  }
126
126
 
127
- if (validationResult.valid) {
127
+ if (validationResult.isValid) {
128
128
  await onApply(trimmedCode);
129
129
  // Reset state after successful apply
130
130
  setCode("");
@@ -148,7 +148,7 @@ export function DiscountCodeInput({
148
148
 
149
149
  // Format discount value for display
150
150
  const getDiscountPreview = () => {
151
- if (!validationResult?.valid) return null;
151
+ if (!validationResult?.isValid) return null;
152
152
 
153
153
  if (validationResult.estimatedSavings) {
154
154
  return t("savings", { savings: formatPrice(validationResult.estimatedSavings) });
@@ -185,7 +185,7 @@ export function DiscountCodeInput({
185
185
  disabled={disabled || isLoading}
186
186
  className={cn(
187
187
  "pl-9 pr-8 uppercase",
188
- validationResult?.valid && "border-green-500 focus-visible:ring-green-500",
188
+ validationResult?.isValid && "border-green-500 focus-visible:ring-green-500",
189
189
  error && "border-destructive focus-visible:ring-destructive"
190
190
  )}
191
191
  />
@@ -203,11 +203,11 @@ export function DiscountCodeInput({
203
203
  type="button"
204
204
  onClick={handleApply}
205
205
  disabled={disabled || isLoading || !code.trim()}
206
- variant={validationResult?.valid ? "default" : "secondary"}
206
+ variant={validationResult?.isValid ? "default" : "secondary"}
207
207
  >
208
208
  {isLoading ? (
209
209
  <Loader2 className="h-4 w-4 animate-spin" />
210
- ) : validationResult?.valid ? (
210
+ ) : validationResult?.isValid ? (
211
211
  <>
212
212
  <Check className="mr-2 h-4 w-4" />
213
213
  {tc("apply")}
@@ -219,7 +219,7 @@ export function DiscountCodeInput({
219
219
  </div>
220
220
 
221
221
  {/* Validation result - success preview */}
222
- {validationResult?.valid && (
222
+ {validationResult?.isValid && (
223
223
  <Alert className="border-green-500 bg-green-50 dark:bg-green-950/20">
224
224
  <Check className="h-4 w-4 text-green-600" />
225
225
  <AlertDescription className="text-green-700 dark:text-green-400">
@@ -24,7 +24,7 @@ type GiftCardStatus = "ACTIVE" | "USED" | "EXPIRED" | "DISABLED";
24
24
  * Gift card validation result
25
25
  */
26
26
  export interface GiftCardValidationResult {
27
- valid: boolean;
27
+ isValid: boolean;
28
28
  code: string;
29
29
  availableBalance?: {
30
30
  amount: string;
@@ -144,7 +144,7 @@ export function GiftCardInput({
144
144
  const result = await onValidate(code);
145
145
  setValidation(result);
146
146
 
147
- if (!result.valid && result.error) {
147
+ if (!result.isValid && result.error) {
148
148
  setError(result.error.message);
149
149
  }
150
150
  } catch (err: unknown) {
@@ -158,7 +158,7 @@ export function GiftCardInput({
158
158
  * Apply the validated gift card
159
159
  */
160
160
  const handleApply = useCallback(() => {
161
- if (validation?.valid) {
161
+ if (validation?.isValid) {
162
162
  onApply(code, validation);
163
163
  setCode("");
164
164
  setValidation(null);
@@ -172,7 +172,7 @@ export function GiftCardInput({
172
172
  (e: React.KeyboardEvent) => {
173
173
  if (e.key === "Enter") {
174
174
  e.preventDefault();
175
- if (validation?.valid) {
175
+ if (validation?.isValid) {
176
176
  handleApply();
177
177
  } else {
178
178
  handleValidate();
@@ -201,13 +201,13 @@ export function GiftCardInput({
201
201
  </div>
202
202
  <Button
203
203
  type="button"
204
- variant={validation?.valid ? "default" : "outline"}
205
- onClick={validation?.valid ? handleApply : handleValidate}
204
+ variant={validation?.isValid ? "default" : "outline"}
205
+ onClick={validation?.isValid ? handleApply : handleValidate}
206
206
  disabled={disabled || isValidating || !code}
207
207
  >
208
208
  {isValidating ? (
209
209
  <Loader2 className="h-4 w-4 animate-spin" />
210
- ) : validation?.valid ? (
210
+ ) : validation?.isValid ? (
211
211
  <>
212
212
  <Check className="mr-2 h-4 w-4" />
213
213
  {tc("apply")}
@@ -223,12 +223,12 @@ export function GiftCardInput({
223
223
  <div
224
224
  className={cn(
225
225
  "rounded-lg p-3 text-sm",
226
- validation.valid
226
+ validation.isValid
227
227
  ? "bg-green-50 dark:bg-green-900/20 text-green-800 dark:text-green-200"
228
228
  : "bg-red-50 dark:bg-red-900/20 text-red-800 dark:text-red-200"
229
229
  )}
230
230
  >
231
- {validation.valid ? (
231
+ {validation.isValid ? (
232
232
  <div className="flex items-center justify-between">
233
233
  <div className="flex items-center gap-2">
234
234
  <Check className="h-4 w-4" />
@@ -9,7 +9,7 @@ export interface DeliveryEstimateProps {
9
9
  latestDate?: string;
10
10
  address?: {
11
11
  city: string;
12
- province: string;
12
+ state: string;
13
13
  country: string;
14
14
  };
15
15
  className?: string;
@@ -97,7 +97,7 @@ export function DeliveryEstimate({
97
97
  <div className="flex items-start gap-2 text-sm text-muted-foreground">
98
98
  <MapPin className="h-4 w-4 mt-0.5 flex-shrink-0" />
99
99
  <span>
100
- Delivering to {address.city}, {address.province},{" "}
100
+ Delivering to {address.city}, {address.state},{" "}
101
101
  {address.country}
102
102
  </span>
103
103
  </div>
@@ -45,7 +45,7 @@ export function ProductCard({
45
45
  (tag) => tag.toLowerCase() === "new" || tag.toLowerCase() === "nowość"
46
46
  );
47
47
 
48
- const isOutOfStock = !product.availableForSale;
48
+ const isOutOfStock = !product.isAvailable;
49
49
  const isGiftCard = product.productType === "GIFT_CARD";
50
50
 
51
51
  return (
@@ -84,7 +84,7 @@ export function buildProductJsonLd(
84
84
  url: productUrl,
85
85
  priceCurrency: variant.price.currencyCode,
86
86
  price: variant.price.amount,
87
- availability: variant.availableForSale
87
+ availability: variant.isAvailable
88
88
  ? "https://schema.org/InStock"
89
89
  : "https://schema.org/OutOfStock",
90
90
  ...(variant.sku && { sku: variant.sku }),
@@ -1,42 +1,60 @@
1
1
  'use client';
2
2
 
3
3
  import { useEffect } from 'react';
4
- import { useRouter } from 'next/navigation';
5
4
  import { useAuthStore, useAuthHydrated } from '@doswiftly/storefront-sdk/react';
6
- import { createAuthTokenClient } from '@doswiftly/storefront-sdk';
7
-
8
- const { clearToken } = createAuthTokenClient();
9
5
 
10
6
  /**
11
- * Detects and fixes auth state desync between httpOnly cookie and Zustand store.
7
+ * Hydrate Zustand auth store z httpOnly cookie po mount klienta.
8
+ *
9
+ * Po Iteracji 2 (XSS fix) `accessToken` NIE persistuje w localStorage —
10
+ * token żyje tylko w-memory + httpOnly cookie (browser auto-sent w GraphQL
11
+ * requests). Po refresh page klient nie zna `customer` info bez round-trip.
12
12
  *
13
- * When server says authenticated (cookie exists initialIsAuthenticated: true)
14
- * but store has no accessToken after persist hydration (localStorage empty/cleared),
15
- * the client can't make authenticated GraphQL requests.
13
+ * Hook wywołuje `/api/auth/whoami` (BFF endpoint reads cookie server-side,
14
+ * forward Bearer do backend, return customer + isAuthenticated). Następnie
15
+ * setAuth() aktualizuje store. Auth-protected queries (`enabled: isAuthenticated`)
16
+ * unblockują się po hydration.
16
17
  *
17
- * Fix: clear the stale cookie, reset auth state, redirect to login.
18
- * The user re-authenticates once, which correctly populates both cookie AND localStorage.
18
+ * Token NIE jest body response kontynuuje życie w cookie. Klient operuje
19
+ * na samym `customer` info (display name, avatar etc.) i `isAuthenticated`
20
+ * gate; każdy GraphQL request automatycznie forward'uje cookie.
19
21
  */
20
22
  export function useAuthSync() {
21
- const router = useRouter();
22
- const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
23
- const accessToken = useAuthStore((s) => s.accessToken);
23
+ const setAuth = useAuthStore((s) => s.setAuth);
24
24
  const clearAuth = useAuthStore((s) => s.clearAuth);
25
+ const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
26
+ const customer = useAuthStore((s) => s.customer);
25
27
  const authHydrated = useAuthHydrated();
26
28
 
27
29
  useEffect(() => {
28
30
  if (!authHydrated) return;
29
31
 
30
- // Desync: cookie exists (isAuthenticated: true from server) but no token in store
31
- if (isAuthenticated && !accessToken) {
32
- // Reset store state immediately
33
- clearAuth();
34
- // Clear stale cookie THEN redirect — must await so proxy sees cleared cookie
35
- clearToken()
36
- .catch(() => {})
37
- .finally(() => {
38
- router.push('/auth/login');
39
- });
40
- }
41
- }, [authHydrated, isAuthenticated, accessToken, clearAuth, router]);
32
+ // Skip jeśli store już zhydrate'owany z customer info (po świeżym login flow
33
+ // setAuth populuje store; cookie + store w sync, no-op).
34
+ if (isAuthenticated && customer) return;
35
+
36
+ let cancelled = false;
37
+
38
+ void fetch('/api/auth/whoami', { credentials: 'include' })
39
+ .then((res) => res.json())
40
+ .then((data: { isAuthenticated: boolean; customer: { id: string; email: string; firstName?: string; lastName?: string; phone?: string } | null }) => {
41
+ if (cancelled) return;
42
+
43
+ if (data.isAuthenticated && data.customer) {
44
+ // Token nie wraca w body — store dostaje tylko customer info.
45
+ // Empty `''` accessToken sygnalizuje "auth via cookie" (in-memory state OK,
46
+ // GraphQL requests używają cookie auto-sent przez fetch credentials: include).
47
+ setAuth(data.customer, '');
48
+ } else {
49
+ clearAuth();
50
+ }
51
+ })
52
+ .catch(() => {
53
+ // Network error — leave store as-is, retry on next mount
54
+ });
55
+
56
+ return () => {
57
+ cancelled = true;
58
+ };
59
+ }, [authHydrated, isAuthenticated, customer, setAuth, clearAuth]);
42
60
  }
@@ -42,7 +42,7 @@ export function useCartActions() {
42
42
  ) => {
43
43
  await api.getState().addToCart([
44
44
  {
45
- merchandiseId: variantId,
45
+ variantId: variantId,
46
46
  quantity,
47
47
  ...(attributeSelections && attributeSelections.length > 0
48
48
  ? { attributeSelections }
@@ -9,7 +9,7 @@ import type { CartLineFields } from "@/lib/graphql/fragments";
9
9
  /**
10
10
  * Per-line attribute selection surfaced to cart UI (configurator choices).
11
11
  * Mirroruje CartLine.attributeSelections z GraphQL, ale ograniczony do pól
12
- * potrzebnych do wyświetlania — surchargeAmount kumulowany w cost.totalAmount
12
+ * potrzebnych do wyświetlania — surchargeAmount kumulowany w cost.total
13
13
  * (BUNDLED) lub emitowany jako child OrderItem po checkout (SEPARATE_LINE).
14
14
  */
15
15
  export interface CartItemAttributeSelection {
@@ -74,7 +74,7 @@ export function useCartSync() {
74
74
 
75
75
  // Map GraphQL lines to display-friendly items
76
76
  const items: CartItemData[] = (cart?.lines ?? []).map((line: CartLineFields) => {
77
- const merchandiseTitle = line.merchandise.title ?? "";
77
+ const merchandiseTitle = line.variant.title ?? "";
78
78
  // Hide generic variant names like "Default" or "Default Title"
79
79
  const isDefaultVariant = /^default(\s+title)?$/i.test(merchandiseTitle);
80
80
  const productTitle = line.productTitle || merchandiseTitle;
@@ -82,19 +82,19 @@ export function useCartSync() {
82
82
 
83
83
  return {
84
84
  lineId: line.id,
85
- variantId: line.merchandise.id,
86
- productId: line.productId || line.merchandise.id,
85
+ variantId: line.variant.id,
86
+ productId: line.productId || line.variant.id,
87
87
  productHandle: line.productHandle ?? undefined,
88
88
  productTitle,
89
89
  variantTitle,
90
90
  productType: line.productType ?? undefined,
91
91
  quantity: line.quantity,
92
92
  price: {
93
- amount: line.merchandise.price.amount,
94
- currencyCode: line.merchandise.price.currencyCode,
93
+ amount: line.variant.price.amount,
94
+ currencyCode: line.variant.price.currencyCode,
95
95
  },
96
- image: line.merchandise.image || null,
97
- available: line.merchandise.available,
96
+ image: line.variant.image || null,
97
+ available: line.variant.isAvailable,
98
98
  // Faza 1 — configurator selections (backend snapshot).
99
99
  attributeSelections: (line.attributeSelections ?? []).map((sel) => ({
100
100
  attributeDefinitionId: sel.attributeDefinitionId,
@@ -108,17 +108,17 @@ export function useCartSync() {
108
108
  });
109
109
 
110
110
  const totalQuantity = cart?.totalQuantity ?? 0;
111
- const subtotal = cart?.cost?.subtotalAmount
112
- ? parseFloat(cart.cost.subtotalAmount.amount)
111
+ const subtotal = cart?.cost?.subtotal
112
+ ? parseFloat(cart.cost.subtotal.amount)
113
113
  : 0;
114
- const total = cart?.cost?.totalAmount
115
- ? parseFloat(cart.cost.totalAmount.amount)
114
+ const total = cart?.cost?.total
115
+ ? parseFloat(cart.cost.total.amount)
116
116
  : subtotal;
117
- const currency = cart?.cost?.subtotalAmount?.currencyCode ?? "PLN";
117
+ const currency = cart?.cost?.subtotal?.currencyCode ?? "PLN";
118
118
 
119
119
  // Discount data from server
120
120
  const discountCodes: string[] = (cart?.discountCodes ?? [])
121
- .filter((dc) => dc.applicable)
121
+ .filter((dc) => dc.isApplicable)
122
122
  .map((dc) => dc.code);
123
123
  const totalDiscount = subtotal - total;
124
124
 
@@ -925,15 +925,14 @@ export function useCustomer(
925
925
  options?: Omit<UseQueryOptions<CustomerQuery>, 'queryKey' | 'queryFn' | 'enabled'>
926
926
  ) {
927
927
  const execute = useExecute();
928
- const accessToken = useAuthStore((s) => s.accessToken);
928
+ // Token nie jest argumentem mutacji (auth context via httpOnly cookie / Bearer header).
929
+ // `isAuthenticated` flag (persisted) używamy do gate query — uniknąć round-trip dla guesta.
930
+ const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
929
931
 
930
932
  return useQuery({
931
- queryKey: queryKeys.customer.detail(accessToken || ''),
932
- queryFn: () =>
933
- execute<CustomerQuery>(CustomerDocument.toString(), {
934
- customerAccessToken: accessToken!,
935
- } as CustomerQueryVariables),
936
- enabled: !!accessToken,
933
+ queryKey: queryKeys.customer.detail(isAuthenticated ? 'me' : 'guest'),
934
+ queryFn: () => execute<CustomerQuery>(CustomerDocument.toString()),
935
+ enabled: isAuthenticated,
937
936
  ...options,
938
937
  });
939
938
  }
@@ -946,15 +945,12 @@ export function useCustomerProfile(
946
945
  options?: Omit<UseQueryOptions<CustomerProfileQuery>, 'queryKey' | 'queryFn' | 'enabled'>
947
946
  ) {
948
947
  const execute = useExecute();
949
- const accessToken = useAuthStore((s) => s.accessToken);
948
+ const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
950
949
 
951
950
  return useQuery({
952
- queryKey: queryKeys.customer.detail(accessToken || ''),
953
- queryFn: () =>
954
- execute<CustomerProfileQuery>(CustomerProfileDocument.toString(), {
955
- customerAccessToken: accessToken!,
956
- } as CustomerProfileQueryVariables),
957
- enabled: !!accessToken,
951
+ queryKey: queryKeys.customer.detail(isAuthenticated ? 'me' : 'guest'),
952
+ queryFn: () => execute<CustomerProfileQuery>(CustomerProfileDocument.toString()),
953
+ enabled: isAuthenticated,
958
954
  ...options,
959
955
  });
960
956
  }
@@ -968,7 +964,7 @@ export function useCustomerOrder(
968
964
  options?: Omit<UseQueryOptions<CustomerOrderQuery>, 'queryKey' | 'queryFn' | 'enabled'>
969
965
  ) {
970
966
  const execute = useExecute();
971
- const accessToken = useAuthStore((s) => s.accessToken);
967
+ const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
972
968
  const currency = useCurrencyStore((s) => s.currency);
973
969
 
974
970
  return useQuery({
@@ -976,9 +972,8 @@ export function useCustomerOrder(
976
972
  queryFn: () =>
977
973
  execute<CustomerOrderQuery>(CustomerOrderDocument.toString(), {
978
974
  orderId,
979
- customerAccessToken: accessToken!,
980
975
  } as CustomerOrderQueryVariables),
981
- enabled: !!accessToken && !!orderId,
976
+ enabled: isAuthenticated && !!orderId,
982
977
  ...options,
983
978
  });
984
979
  }
@@ -991,14 +986,12 @@ export function useCustomerUpdate(
991
986
  options?: UseMutationOptions<CustomerUpdateMutation, Error, CustomerUpdateInput>
992
987
  ) {
993
988
  const execute = useExecute();
994
- const { accessToken } = useAuthStore();
995
989
  const queryClient = useQueryClient();
996
990
 
997
991
  return useMutation({
998
992
  mutationFn: (customer: CustomerUpdateInput) =>
999
993
  execute<CustomerUpdateMutation>(CustomerUpdateDocument.toString(), {
1000
994
  customer,
1001
- customerAccessToken: accessToken!,
1002
995
  } as CustomerUpdateMutationVariables),
1003
996
  onSettled: () => {
1004
997
  queryClient.invalidateQueries({ queryKey: queryKeys.customer.all() });
@@ -1014,14 +1007,13 @@ export function useCustomerAddressCreate(
1014
1007
  options?: UseMutationOptions<CustomerAddressCreateMutation, Error, { address: Record<string, unknown> }>
1015
1008
  ) {
1016
1009
  const execute = useExecute();
1017
- const { accessToken } = useAuthStore();
1018
1010
  const queryClient = useQueryClient();
1019
1011
 
1020
1012
  return useMutation({
1021
1013
  mutationFn: ({ address }: { address: Record<string, unknown> }) =>
1022
1014
  execute<CustomerAddressCreateMutation>(
1023
1015
  CustomerAddressCreateDocument.toString(),
1024
- { address, customerAccessToken: accessToken! } as CustomerAddressCreateMutationVariables,
1016
+ { address } as CustomerAddressCreateMutationVariables,
1025
1017
  ),
1026
1018
  onSettled: () => {
1027
1019
  queryClient.invalidateQueries({ queryKey: queryKeys.customer.all() });
@@ -1037,14 +1029,13 @@ export function useCustomerAddressUpdate(
1037
1029
  options?: UseMutationOptions<CustomerAddressUpdateMutation, Error, { id: string; address: Record<string, unknown> }>
1038
1030
  ) {
1039
1031
  const execute = useExecute();
1040
- const { accessToken } = useAuthStore();
1041
1032
  const queryClient = useQueryClient();
1042
1033
 
1043
1034
  return useMutation({
1044
1035
  mutationFn: ({ id, address }: { id: string; address: Record<string, unknown> }) =>
1045
1036
  execute<CustomerAddressUpdateMutation>(
1046
1037
  CustomerAddressUpdateDocument.toString(),
1047
- { id, address, customerAccessToken: accessToken! } as CustomerAddressUpdateMutationVariables,
1038
+ { id, address } as CustomerAddressUpdateMutationVariables,
1048
1039
  ),
1049
1040
  onSettled: () => {
1050
1041
  queryClient.invalidateQueries({ queryKey: queryKeys.customer.all() });
@@ -1060,14 +1051,13 @@ export function useCustomerAddressDelete(
1060
1051
  options?: UseMutationOptions<CustomerAddressDeleteMutation, Error, string>
1061
1052
  ) {
1062
1053
  const execute = useExecute();
1063
- const { accessToken } = useAuthStore();
1064
1054
  const queryClient = useQueryClient();
1065
1055
 
1066
1056
  return useMutation({
1067
1057
  mutationFn: (id: string) =>
1068
1058
  execute<CustomerAddressDeleteMutation>(
1069
1059
  CustomerAddressDeleteDocument.toString(),
1070
- { id, customerAccessToken: accessToken! } as CustomerAddressDeleteMutationVariables,
1060
+ { id } as CustomerAddressDeleteMutationVariables,
1071
1061
  ),
1072
1062
  onSettled: () => {
1073
1063
  queryClient.invalidateQueries({ queryKey: queryKeys.customer.all() });
@@ -1083,14 +1073,13 @@ export function useCustomerDefaultAddressUpdate(
1083
1073
  options?: UseMutationOptions<CustomerDefaultAddressUpdateMutation, Error, string>
1084
1074
  ) {
1085
1075
  const execute = useExecute();
1086
- const { accessToken } = useAuthStore();
1087
1076
  const queryClient = useQueryClient();
1088
1077
 
1089
1078
  return useMutation({
1090
1079
  mutationFn: (addressId: string) =>
1091
1080
  execute<CustomerDefaultAddressUpdateMutation>(
1092
1081
  CustomerDefaultAddressUpdateDocument.toString(),
1093
- { addressId, customerAccessToken: accessToken! } as CustomerDefaultAddressUpdateMutationVariables,
1082
+ { addressId } as CustomerDefaultAddressUpdateMutationVariables,
1094
1083
  ),
1095
1084
  onSettled: () => {
1096
1085
  queryClient.invalidateQueries({ queryKey: queryKeys.customer.all() });
@@ -1144,13 +1133,13 @@ export function useLoyaltyMember(
1144
1133
  options?: Omit<UseQueryOptions<LoyaltyMemberQuery>, 'queryKey' | 'queryFn' | 'enabled'>
1145
1134
  ) {
1146
1135
  const execute = useExecute();
1147
- const accessToken = useAuthStore((s) => s.accessToken);
1136
+ const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
1148
1137
  const currency = useCurrencyStore((s) => s.currency);
1149
1138
 
1150
1139
  return useQuery({
1151
1140
  queryKey: queryKeys.loyalty.member(currency),
1152
1141
  queryFn: () => execute<LoyaltyMemberQuery>(LoyaltyMemberDocument.toString()),
1153
- enabled: !!accessToken,
1142
+ enabled: isAuthenticated,
1154
1143
  ...options,
1155
1144
  });
1156
1145
  }
@@ -1162,13 +1151,13 @@ export function useLoyaltyRewards(
1162
1151
  options?: Omit<UseQueryOptions<LoyaltyRewardsQuery>, 'queryKey' | 'queryFn' | 'enabled'>
1163
1152
  ) {
1164
1153
  const execute = useExecute();
1165
- const accessToken = useAuthStore((s) => s.accessToken);
1154
+ const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
1166
1155
  const currency = useCurrencyStore((s) => s.currency);
1167
1156
 
1168
1157
  return useQuery({
1169
1158
  queryKey: queryKeys.loyalty.rewards(currency),
1170
1159
  queryFn: () => execute<LoyaltyRewardsQuery>(LoyaltyRewardsDocument.toString()),
1171
- enabled: !!accessToken,
1160
+ enabled: isAuthenticated,
1172
1161
  ...options,
1173
1162
  });
1174
1163
  }
@@ -1181,7 +1170,7 @@ export function useLoyaltyTransactions(
1181
1170
  options?: Omit<UseQueryOptions<LoyaltyTransactionsQuery>, 'queryKey' | 'queryFn' | 'enabled'>
1182
1171
  ) {
1183
1172
  const execute = useExecute();
1184
- const accessToken = useAuthStore((s) => s.accessToken);
1173
+ const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
1185
1174
  const currency = useCurrencyStore((s) => s.currency);
1186
1175
 
1187
1176
  return useQuery({
@@ -1193,7 +1182,7 @@ export function useLoyaltyTransactions(
1193
1182
  };
1194
1183
  return execute<LoyaltyTransactionsQuery>(LoyaltyTransactionsDocument.toString(), graphqlVariables);
1195
1184
  },
1196
- enabled: !!accessToken,
1185
+ enabled: isAuthenticated,
1197
1186
  ...options,
1198
1187
  });
1199
1188
  }
@@ -1220,12 +1209,12 @@ export function useReferralStats(
1220
1209
  options?: Omit<UseQueryOptions<ReferralStatsQuery>, 'queryKey' | 'queryFn' | 'enabled'>
1221
1210
  ) {
1222
1211
  const execute = useExecute();
1223
- const accessToken = useAuthStore((s) => s.accessToken);
1212
+ const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
1224
1213
 
1225
1214
  return useQuery({
1226
1215
  queryKey: queryKeys.loyalty.referralStats(),
1227
1216
  queryFn: () => execute<ReferralStatsQuery>(ReferralStatsDocument.toString()),
1228
- enabled: !!accessToken,
1217
+ enabled: isAuthenticated,
1229
1218
  ...options,
1230
1219
  });
1231
1220
  }
@@ -203,11 +203,28 @@ export const fetchAvailableFilters = cache(async (
203
203
  });
204
204
 
205
205
  /**
206
- * Fetch customer data (requires access token)
206
+ * Fetch customer data (requires authenticated session — httpOnly cookie OR explicit Bearer token).
207
+ *
208
+ * Server Components: czytaj `customerAccessToken` cookie via Next.js `cookies()` API,
209
+ * forward jako `Authorization: Bearer` do backend. Klient nie ma JS-access do cookie
210
+ * (XSS-safe), ale Next.js server może go odczytać via `cookies().get()`.
211
+ *
212
+ * @param accessToken Optional — gdy SSR helper ma już token (np. po login redirect SSR);
213
+ * domyślnie czyta z `cookies()` (App Router).
207
214
  */
208
- export const fetchCustomer = cache(async (accessToken: string): Promise<CustomerQuery> => {
209
- const variables: CustomerQueryVariables = { customerAccessToken: accessToken };
210
- return request(CustomerDocument, variables);
215
+ export const fetchCustomer = cache(async (accessToken?: string): Promise<CustomerQuery> => {
216
+ let token = accessToken;
217
+ if (!token) {
218
+ const { cookies } = await import('next/headers');
219
+ const cookieStore = await cookies();
220
+ token = cookieStore.get('customerAccessToken')?.value;
221
+ }
222
+
223
+ return request(
224
+ CustomerDocument,
225
+ {} as CustomerQueryVariables,
226
+ token ? { Authorization: `Bearer ${token}` } : undefined,
227
+ );
211
228
  });
212
229
 
213
230
  // ============================================================================
@@ -241,15 +241,15 @@
241
241
  "firstName": "First Name",
242
242
  "lastName": "Last Name",
243
243
  "company": "Company",
244
- "address1": "Address",
245
- "address1Placeholder": "Street and number",
246
- "address2": "Apartment, floor",
247
- "zip": "Postal Code",
248
- "zipPlaceholder": "00000",
244
+ "streetLine1": "Address",
245
+ "streetLine1Placeholder": "Street and number",
246
+ "streetLine2": "Apartment, floor",
247
+ "postalCode": "Postal Code",
248
+ "postalCodePlaceholder": "00000",
249
249
  "city": "City",
250
250
  "country": "Country",
251
251
  "countryPlaceholder": "Select country",
252
- "province": "State/Province",
252
+ "state": "State/Province",
253
253
  "phone": "Phone"
254
254
  },
255
255
  "validation": {