@doswiftly/cli 0.2.5 → 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 (33) hide show
  1. package/CHANGELOG.md +899 -0
  2. package/dist/lib/proxy-server.d.ts.map +1 -1
  3. package/dist/lib/proxy-server.js +20 -7
  4. package/dist/lib/proxy-server.js.map +1 -1
  5. package/package.json +4 -2
  6. package/templates/storefront-nextjs-shadcn/app/[locale]/account/addresses/page.tsx +10 -10
  7. package/templates/storefront-nextjs-shadcn/app/[locale]/account/orders/[id]/page.tsx +9 -9
  8. package/templates/storefront-nextjs-shadcn/app/[locale]/account/orders/[id]/tracking/page.tsx +1 -1
  9. package/templates/storefront-nextjs-shadcn/app/[locale]/account/orders/page.tsx +2 -2
  10. package/templates/storefront-nextjs-shadcn/app/[locale]/account/settings/page.tsx +3 -3
  11. package/templates/storefront-nextjs-shadcn/app/[locale]/checkout/page.tsx +41 -41
  12. package/templates/storefront-nextjs-shadcn/app/[locale]/products/[slug]/product-client.tsx +1 -1
  13. package/templates/storefront-nextjs-shadcn/app/api/auth/whoami/route.ts +6 -0
  14. package/templates/storefront-nextjs-shadcn/components/account/address-form.tsx +43 -43
  15. package/templates/storefront-nextjs-shadcn/components/account/address-list.tsx +9 -9
  16. package/templates/storefront-nextjs-shadcn/components/account/customer-info.fragment.graphql +5 -5
  17. package/templates/storefront-nextjs-shadcn/components/account/order-details.tsx +8 -8
  18. package/templates/storefront-nextjs-shadcn/components/account/order-history.tsx +2 -2
  19. package/templates/storefront-nextjs-shadcn/components/account/order-summary.fragment.graphql +15 -13
  20. package/templates/storefront-nextjs-shadcn/components/cart/cart-line.fragment.graphql +6 -6
  21. package/templates/storefront-nextjs-shadcn/components/discount/discount-code-input.tsx +10 -10
  22. package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-input.tsx +9 -9
  23. package/templates/storefront-nextjs-shadcn/components/order/delivery-estimate.tsx +2 -2
  24. package/templates/storefront-nextjs-shadcn/components/product/product-card.tsx +1 -1
  25. package/templates/storefront-nextjs-shadcn/components/seo/product-json-ld.ts +1 -1
  26. package/templates/storefront-nextjs-shadcn/hooks/use-auth-sync.ts +43 -25
  27. package/templates/storefront-nextjs-shadcn/hooks/use-cart-actions.ts +1 -1
  28. package/templates/storefront-nextjs-shadcn/hooks/use-cart-sync.ts +14 -14
  29. package/templates/storefront-nextjs-shadcn/lib/graphql/hooks.ts +24 -35
  30. package/templates/storefront-nextjs-shadcn/lib/graphql/server.ts +21 -4
  31. package/templates/storefront-nextjs-shadcn/messages/en.json +6 -6
  32. package/templates/storefront-nextjs-shadcn/messages/pl.json +6 -6
  33. package/templates/storefront-nextjs-shadcn/stores/checkout-store.ts +8 -8
@@ -11,11 +11,11 @@ export interface AddressFormData {
11
11
  firstName: string;
12
12
  lastName: string;
13
13
  company?: string;
14
- address1: string;
15
- address2?: string;
14
+ streetLine1: string;
15
+ streetLine2?: string;
16
16
  city: string;
17
- province: string;
18
- zip: string;
17
+ state: string;
18
+ postalCode: string;
19
19
  country: string;
20
20
  phone?: string;
21
21
  isDefault?: boolean;
@@ -48,11 +48,11 @@ export function AddressForm({
48
48
  firstName: "",
49
49
  lastName: "",
50
50
  company: "",
51
- address1: "",
52
- address2: "",
51
+ streetLine1: "",
52
+ streetLine2: "",
53
53
  city: "",
54
- province: "",
55
- zip: "",
54
+ state: "",
55
+ postalCode: "",
56
56
  country: "Poland",
57
57
  phone: "",
58
58
  isDefault: false,
@@ -78,17 +78,17 @@ export function AddressForm({
78
78
  if (!formData.lastName.trim()) {
79
79
  newErrors.lastName = t("validation.lastNameRequired");
80
80
  }
81
- if (!formData.address1.trim()) {
82
- newErrors.address1 = t("validation.addressRequired");
81
+ if (!formData.streetLine1.trim()) {
82
+ newErrors.streetLine1 = t("validation.addressRequired");
83
83
  }
84
84
  if (!formData.city.trim()) {
85
85
  newErrors.city = t("validation.cityRequired");
86
86
  }
87
- if (!formData.province.trim()) {
88
- newErrors.province = t("validation.provinceRequired");
87
+ if (!formData.state.trim()) {
88
+ newErrors.state = t("validation.provinceRequired");
89
89
  }
90
- if (!formData.zip.trim()) {
91
- newErrors.zip = t("validation.zipRequired");
90
+ if (!formData.postalCode.trim()) {
91
+ newErrors.postalCode = t("validation.zipRequired");
92
92
  }
93
93
  if (!formData.country.trim()) {
94
94
  newErrors.country = t("validation.countryRequired");
@@ -175,37 +175,37 @@ export function AddressForm({
175
175
  {/* Address 1 */}
176
176
  <div>
177
177
  <label
178
- htmlFor="address1"
178
+ htmlFor="streetLine1"
179
179
  className="block text-sm font-medium text-foreground mb-1"
180
180
  >
181
- {t("address.address1")} *
181
+ {t("address.streetLine1")} *
182
182
  </label>
183
183
  <Input
184
- id="address1"
184
+ id="streetLine1"
185
185
  type="text"
186
- value={formData.address1}
187
- onChange={(e) => handleChange("address1", e.target.value)}
186
+ value={formData.streetLine1}
187
+ onChange={(e) => handleChange("streetLine1", e.target.value)}
188
188
  disabled={isLoading}
189
- className={errors.address1 ? "border-red-500" : ""}
189
+ className={errors.streetLine1 ? "border-red-500" : ""}
190
190
  />
191
- {errors.address1 && (
192
- <p className="mt-1 text-sm text-red-600">{errors.address1}</p>
191
+ {errors.streetLine1 && (
192
+ <p className="mt-1 text-sm text-red-600">{errors.streetLine1}</p>
193
193
  )}
194
194
  </div>
195
195
 
196
196
  {/* Address 2 */}
197
197
  <div>
198
198
  <label
199
- htmlFor="address2"
199
+ htmlFor="streetLine2"
200
200
  className="block text-sm font-medium text-foreground mb-1"
201
201
  >
202
- {`${t("address.address2")} (${tc("optional")})`}
202
+ {`${t("address.streetLine2")} (${tc("optional")})`}
203
203
  </label>
204
204
  <Input
205
- id="address2"
205
+ id="streetLine2"
206
206
  type="text"
207
- value={formData.address2 || ""}
208
- onChange={(e) => handleChange("address2", e.target.value)}
207
+ value={formData.streetLine2 || ""}
208
+ onChange={(e) => handleChange("streetLine2", e.target.value)}
209
209
  disabled={isLoading}
210
210
  />
211
211
  </div>
@@ -235,42 +235,42 @@ export function AddressForm({
235
235
  {/* Province */}
236
236
  <div>
237
237
  <label
238
- htmlFor="province"
238
+ htmlFor="state"
239
239
  className="block text-sm font-medium text-foreground mb-1"
240
240
  >
241
- {t("address.province")} *
241
+ {t("address.state")} *
242
242
  </label>
243
243
  <Input
244
- id="province"
244
+ id="state"
245
245
  type="text"
246
- value={formData.province}
247
- onChange={(e) => handleChange("province", e.target.value)}
246
+ value={formData.state}
247
+ onChange={(e) => handleChange("state", e.target.value)}
248
248
  disabled={isLoading}
249
- className={errors.province ? "border-red-500" : ""}
249
+ className={errors.state ? "border-red-500" : ""}
250
250
  />
251
- {errors.province && (
252
- <p className="mt-1 text-sm text-red-600">{errors.province}</p>
251
+ {errors.state && (
252
+ <p className="mt-1 text-sm text-red-600">{errors.state}</p>
253
253
  )}
254
254
  </div>
255
255
 
256
256
  {/* Postal Code */}
257
257
  <div>
258
258
  <label
259
- htmlFor="zip"
259
+ htmlFor="postalCode"
260
260
  className="block text-sm font-medium text-foreground mb-1"
261
261
  >
262
- {t("address.zip")} *
262
+ {t("address.postalCode")} *
263
263
  </label>
264
264
  <Input
265
- id="zip"
265
+ id="postalCode"
266
266
  type="text"
267
- value={formData.zip}
268
- onChange={(e) => handleChange("zip", e.target.value)}
267
+ value={formData.postalCode}
268
+ onChange={(e) => handleChange("postalCode", e.target.value)}
269
269
  disabled={isLoading}
270
- className={errors.zip ? "border-red-500" : ""}
270
+ className={errors.postalCode ? "border-red-500" : ""}
271
271
  />
272
- {errors.zip && (
273
- <p className="mt-1 text-sm text-red-600">{errors.zip}</p>
272
+ {errors.postalCode && (
273
+ <p className="mt-1 text-sm text-red-600">{errors.postalCode}</p>
274
274
  )}
275
275
  </div>
276
276
  </div>
@@ -11,11 +11,11 @@ export interface Address {
11
11
  firstName: string;
12
12
  lastName: string;
13
13
  company?: string;
14
- address1: string;
15
- address2?: string;
14
+ streetLine1: string;
15
+ streetLine2?: string;
16
16
  city: string;
17
- province: string;
18
- zip: string;
17
+ state: string;
18
+ postalCode: string;
19
19
  country: string;
20
20
  phone?: string;
21
21
  isDefault: boolean;
@@ -45,7 +45,7 @@ export function AddressList({
45
45
 
46
46
  const handleDelete = (id: string, address: Address) => {
47
47
  if (
48
- confirm(t("deleteAddressConfirm", { address: address.address1 }))
48
+ confirm(t("deleteAddressConfirm", { address: address.streetLine1 }))
49
49
  ) {
50
50
  onDelete?.(id);
51
51
  }
@@ -114,12 +114,12 @@ export function AddressList({
114
114
  {address.company && (
115
115
  <p className="text-muted-foreground">{address.company}</p>
116
116
  )}
117
- <p className="text-muted-foreground">{address.address1}</p>
118
- {address.address2 && (
119
- <p className="text-muted-foreground">{address.address2}</p>
117
+ <p className="text-muted-foreground">{address.streetLine1}</p>
118
+ {address.streetLine2 && (
119
+ <p className="text-muted-foreground">{address.streetLine2}</p>
120
120
  )}
121
121
  <p className="text-muted-foreground">
122
- {address.city}, {address.province} {address.zip}
122
+ {address.city}, {address.state} {address.postalCode}
123
123
  </p>
124
124
  <p className="text-muted-foreground">{address.country}</p>
125
125
  {address.phone && (
@@ -14,7 +14,7 @@ fragment CustomerInfoFields on Customer {
14
14
  lastName
15
15
  displayName
16
16
  phone
17
- emailVerified
17
+ isEmailVerified
18
18
  ordersCount
19
19
  totalSpent {
20
20
  amount
@@ -24,11 +24,11 @@ fragment CustomerInfoFields on Customer {
24
24
  id
25
25
  firstName
26
26
  lastName
27
- address1
28
- address2
27
+ streetLine1
28
+ streetLine2
29
29
  city
30
- province
31
- zip
30
+ state
31
+ postalCode
32
32
  country
33
33
  phone
34
34
  }
@@ -20,11 +20,11 @@ export interface OrderAddress {
20
20
  firstName: string;
21
21
  lastName: string;
22
22
  company?: string;
23
- address1: string;
24
- address2?: string;
23
+ streetLine1: string;
24
+ streetLine2?: string;
25
25
  city: string;
26
- province: string;
27
- zip: string;
26
+ state: string;
27
+ postalCode: string;
28
28
  country: string;
29
29
  phone?: string;
30
30
  }
@@ -111,12 +111,12 @@ export function OrderDetails({ order, className }: OrderDetailsProps) {
111
111
  {address.company && (
112
112
  <p className="text-muted-foreground">{address.company}</p>
113
113
  )}
114
- <p className="text-muted-foreground">{address.address1}</p>
115
- {address.address2 && (
116
- <p className="text-muted-foreground">{address.address2}</p>
114
+ <p className="text-muted-foreground">{address.streetLine1}</p>
115
+ {address.streetLine2 && (
116
+ <p className="text-muted-foreground">{address.streetLine2}</p>
117
117
  )}
118
118
  <p className="text-muted-foreground">
119
- {address.city}, {address.province} {address.zip}
119
+ {address.city}, {address.state} {address.postalCode}
120
120
  </p>
121
121
  <p className="text-muted-foreground">{address.country}</p>
122
122
  {address.phone && (
@@ -97,7 +97,7 @@ export function OrderHistory({ orders, className }: OrderHistoryProps) {
97
97
  <div className="space-y-1 text-sm text-muted-foreground">
98
98
  <p>{t("placedOn")} {formatDate(order.processedAt)}</p>
99
99
  <p>
100
- {t("itemCount", { count: order.lineItemsCount })}
100
+ {t("itemCount", { count: order.itemCount })}
101
101
  </p>
102
102
  </div>
103
103
  </div>
@@ -105,7 +105,7 @@ export function OrderHistory({ orders, className }: OrderHistoryProps) {
105
105
  <div className="text-right">
106
106
  <p className="text-sm text-muted-foreground">{t("orderTotal")}</p>
107
107
  <p className="text-lg font-semibold text-foreground">
108
- {formatAmount(order.totalPrice.amount, order.totalPrice.currencyCode)}
108
+ {formatAmount(order.totals.total.amount, order.totals.total.currencyCode)}
109
109
  </p>
110
110
  </div>
111
111
  <ChevronRight className="h-5 w-5 text-muted-foreground" />
@@ -11,21 +11,23 @@ fragment OrderSummaryFields on Order {
11
11
  id
12
12
  orderNumber
13
13
  status
14
- financialStatus
14
+ paymentStatus
15
15
  fulfillmentStatus
16
16
  processedAt
17
- lineItemsCount
18
- totalPrice {
19
- amount
20
- currencyCode
21
- }
22
- subtotalPrice {
23
- amount
24
- currencyCode
25
- }
26
- totalShipping {
27
- amount
28
- currencyCode
17
+ itemCount
18
+ totals {
19
+ total {
20
+ amount
21
+ currencyCode
22
+ }
23
+ subtotal {
24
+ amount
25
+ currencyCode
26
+ }
27
+ totalShipping {
28
+ amount
29
+ currencyCode
30
+ }
29
31
  }
30
32
  shippingAddress {
31
33
  firstName
@@ -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 }