@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.
- package/CHANGELOG.md +639 -0
- package/package.json +1 -1
- package/templates/storefront-nextjs-shadcn/app/[locale]/account/addresses/page.tsx +10 -10
- package/templates/storefront-nextjs-shadcn/app/[locale]/account/orders/[id]/page.tsx +9 -9
- package/templates/storefront-nextjs-shadcn/app/[locale]/account/orders/[id]/tracking/page.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/app/[locale]/account/orders/page.tsx +2 -2
- package/templates/storefront-nextjs-shadcn/app/[locale]/account/settings/page.tsx +3 -3
- package/templates/storefront-nextjs-shadcn/app/[locale]/checkout/page.tsx +41 -41
- package/templates/storefront-nextjs-shadcn/app/[locale]/products/[slug]/product-client.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/app/api/auth/whoami/route.ts +6 -0
- package/templates/storefront-nextjs-shadcn/components/account/address-form.tsx +43 -43
- package/templates/storefront-nextjs-shadcn/components/account/address-list.tsx +9 -9
- package/templates/storefront-nextjs-shadcn/components/account/customer-info.fragment.graphql +5 -5
- package/templates/storefront-nextjs-shadcn/components/account/order-details.tsx +8 -8
- package/templates/storefront-nextjs-shadcn/components/account/order-history.tsx +2 -2
- package/templates/storefront-nextjs-shadcn/components/account/order-summary.fragment.graphql +15 -13
- package/templates/storefront-nextjs-shadcn/components/cart/cart-line.fragment.graphql +6 -6
- package/templates/storefront-nextjs-shadcn/components/discount/discount-code-input.tsx +10 -10
- package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-input.tsx +9 -9
- package/templates/storefront-nextjs-shadcn/components/order/delivery-estimate.tsx +2 -2
- package/templates/storefront-nextjs-shadcn/components/product/product-card.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/seo/product-json-ld.ts +1 -1
- package/templates/storefront-nextjs-shadcn/hooks/use-auth-sync.ts +43 -25
- package/templates/storefront-nextjs-shadcn/hooks/use-cart-actions.ts +1 -1
- package/templates/storefront-nextjs-shadcn/hooks/use-cart-sync.ts +14 -14
- package/templates/storefront-nextjs-shadcn/lib/graphql/hooks.ts +24 -35
- package/templates/storefront-nextjs-shadcn/lib/graphql/server.ts +21 -4
- package/templates/storefront-nextjs-shadcn/messages/en.json +6 -6
- package/templates/storefront-nextjs-shadcn/messages/pl.json +6 -6
- 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
|
-
|
|
21
|
+
variant {
|
|
22
22
|
id
|
|
23
23
|
title
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
41
|
+
pricePerUnit {
|
|
42
42
|
amount
|
|
43
43
|
currencyCode
|
|
44
44
|
}
|
|
45
|
-
|
|
45
|
+
total {
|
|
46
46
|
amount
|
|
47
47
|
currencyCode
|
|
48
48
|
}
|
|
49
|
-
|
|
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
|
-
|
|
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.
|
|
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({
|
|
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?.
|
|
122
|
+
if (!validationResult?.isValid) {
|
|
123
123
|
await handleValidate();
|
|
124
124
|
return;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
if (validationResult.
|
|
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?.
|
|
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?.
|
|
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?.
|
|
206
|
+
variant={validationResult?.isValid ? "default" : "secondary"}
|
|
207
207
|
>
|
|
208
208
|
{isLoading ? (
|
|
209
209
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
210
|
-
) : validationResult?.
|
|
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?.
|
|
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
|
-
|
|
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.
|
|
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?.
|
|
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?.
|
|
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?.
|
|
205
|
-
onClick={validation?.
|
|
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?.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
*
|
|
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
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
|
-
*
|
|
18
|
-
*
|
|
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
|
|
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
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
}
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
86
|
-
productId: line.productId || line.
|
|
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.
|
|
94
|
-
currencyCode: line.
|
|
93
|
+
amount: line.variant.price.amount,
|
|
94
|
+
currencyCode: line.variant.price.currencyCode,
|
|
95
95
|
},
|
|
96
|
-
image: line.
|
|
97
|
-
available: line.
|
|
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?.
|
|
112
|
-
? parseFloat(cart.cost.
|
|
111
|
+
const subtotal = cart?.cost?.subtotal
|
|
112
|
+
? parseFloat(cart.cost.subtotal.amount)
|
|
113
113
|
: 0;
|
|
114
|
-
const total = cart?.cost?.
|
|
115
|
-
? parseFloat(cart.cost.
|
|
114
|
+
const total = cart?.cost?.total
|
|
115
|
+
? parseFloat(cart.cost.total.amount)
|
|
116
116
|
: subtotal;
|
|
117
|
-
const currency = cart?.cost?.
|
|
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.
|
|
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
|
-
|
|
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(
|
|
932
|
-
queryFn: () =>
|
|
933
|
-
|
|
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
|
|
948
|
+
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
|
|
950
949
|
|
|
951
950
|
return useQuery({
|
|
952
|
-
queryKey: queryKeys.customer.detail(
|
|
953
|
-
queryFn: () =>
|
|
954
|
-
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
"
|
|
245
|
-
"
|
|
246
|
-
"
|
|
247
|
-
"
|
|
248
|
-
"
|
|
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
|
-
"
|
|
252
|
+
"state": "State/Province",
|
|
253
253
|
"phone": "Phone"
|
|
254
254
|
},
|
|
255
255
|
"validation": {
|