@kasuvia/sdk 0.0.2 → 0.0.5
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/dist/hooks/index.d.mts +430 -0
- package/dist/hooks/index.d.ts +430 -0
- package/dist/hooks/index.js +3073 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/index.mjs +2962 -0
- package/dist/hooks/index.mjs.map +1 -0
- package/dist/index-CxNLI4nw.d.ts +786 -0
- package/dist/index-CzVMyUs4.d.mts +492 -0
- package/dist/index-CzVMyUs4.d.ts +492 -0
- package/dist/index-Dowl5224.d.mts +786 -0
- package/dist/index.d.mts +48 -1739
- package/dist/index.d.ts +48 -1739
- package/dist/index.js +1544 -2359
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1527 -2258
- package/dist/index.mjs.map +1 -1
- package/dist/sdk-context-BuarJbeb.d.ts +75 -0
- package/dist/sdk-context-Db8kf1Tv.d.mts +75 -0
- package/dist/services/index.d.mts +2 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.js +2369 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/index.mjs +2252 -0
- package/dist/services/index.mjs.map +1 -0
- package/package.json +11 -2
- package/src/config/index.ts +10 -91
- package/src/constants.ts +13 -0
- package/src/context/sdk-context.tsx +70 -0
- package/src/graphql/collections.graphql.ts +2 -10
- package/src/graphql/products.graphql.ts +215 -65
- package/src/graphql/wishlist.graphql.ts +7 -6
- package/src/hooks/index.ts +2 -0
- package/src/hooks/use-auth.ts +82 -158
- package/src/hooks/use-bespoke-availability.ts +25 -8
- package/src/hooks/use-cart.ts +222 -119
- package/src/hooks/use-cms.ts +179 -86
- package/src/hooks/use-collections.ts +52 -15
- package/src/hooks/use-consultation-stream.ts +4 -3
- package/src/hooks/use-consultations.ts +120 -51
- package/src/hooks/use-countries.ts +35 -13
- package/src/hooks/use-currencies.ts +26 -13
- package/src/hooks/use-google-translate.ts +22 -8
- package/src/hooks/use-languages.ts +40 -20
- package/src/hooks/use-mobile.ts +23 -0
- package/src/hooks/use-order-stream.ts +9 -5
- package/src/hooks/use-orders.ts +99 -44
- package/src/hooks/use-payment.ts +108 -40
- package/src/hooks/use-products.ts +251 -53
- package/src/hooks/use-service-booking.ts +152 -90
- package/src/hooks/use-states.ts +35 -15
- package/src/hooks/use-wishlist.ts +92 -91
- package/src/index.ts +26 -30
- package/src/services/auth.service.ts +209 -167
- package/src/services/bespoke.service.ts +12 -4
- package/src/services/cart.service.ts +172 -70
- package/src/services/cms.service.ts +70 -35
- package/src/services/collection.service.ts +22 -5
- package/src/services/config.service.ts +34 -14
- package/src/services/consultation.service.ts +72 -18
- package/src/services/logistics.service.ts +35 -11
- package/src/services/notifications.service.ts +27 -12
- package/src/services/order.service.ts +74 -15
- package/src/services/payment.service.ts +86 -31
- package/src/services/product.service.ts +279 -39
- package/src/services/service-booking.service.ts +98 -26
- package/src/services/wishlist.service.ts +69 -25
- package/src/types/auth/index.ts +6 -4
- package/src/types/business/index.ts +7 -2
- package/src/types/payments/index.ts +3 -3
- package/src/types/services/index.ts +2 -5
- package/src/types/store/index.ts +38 -3
- package/src/utils/error-handler.ts +10 -5
- package/src/utils/storage.ts +48 -26
- package/src/utils/validators.ts +85 -0
- package/src/client/authClient.ts +0 -135
- package/src/client/graphqlClient.ts +0 -53
- package/src/client/httpClient.ts +0 -298
package/src/hooks/use-payment.ts
CHANGED
|
@@ -1,68 +1,136 @@
|
|
|
1
1
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { isInitialized } from "../config";
|
|
2
|
+
import { createDedicatedAccount, initiatePayment, verifyTransaction, getTransactionStatus } from "../services/payment.service";
|
|
3
|
+
import type { SdkConfig } from "../config";
|
|
5
4
|
import type { DVACreateRequest, PaymentInitiateRequest } from "../types/payments";
|
|
6
|
-
|
|
5
|
+
import { logger } from "../utils";
|
|
6
|
+
import { useSdkContext, resolveHookArgs } from "../context/sdk-context";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Hook for creating a Dedicated Virtual Account (DVA)
|
|
10
|
+
* Optional config and accessToken — read from SdkContext if omitted.
|
|
10
11
|
*/
|
|
11
|
-
export function useCreateDVA() {
|
|
12
|
-
const
|
|
12
|
+
export function useCreateDVA(config?: SdkConfig, accessToken?: string | null) {
|
|
13
|
+
const ctx = useSdkContext();
|
|
14
|
+
try {
|
|
15
|
+
const { config: cfg, accessToken: token } = resolveHookArgs(config, accessToken, ctx);
|
|
16
|
+
const queryClient = useQueryClient();
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
return useMutation({
|
|
19
|
+
mutationFn: (request: DVACreateRequest) =>
|
|
20
|
+
token ? createDedicatedAccount(cfg, token, request) : Promise.reject('No access token'),
|
|
21
|
+
onSuccess: () => {
|
|
22
|
+
queryClient.invalidateQueries({ queryKey: ['payment'] });
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
} catch (error) {
|
|
26
|
+
logger.warn('[useCreateDVA] Called outside QueryClientProvider, returning no-op mutation');
|
|
27
|
+
return {
|
|
28
|
+
mutate: () => {},
|
|
29
|
+
mutateAsync: () => Promise.resolve(undefined),
|
|
30
|
+
} as any;
|
|
31
|
+
}
|
|
21
32
|
}
|
|
22
33
|
|
|
23
34
|
/**
|
|
24
35
|
* Hook for initiating card payment
|
|
36
|
+
* Optional config and accessToken — read from SdkContext if omitted.
|
|
25
37
|
*/
|
|
26
|
-
export function useInitiatePayment() {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
38
|
+
export function useInitiatePayment(config?: SdkConfig, accessToken?: string | null) {
|
|
39
|
+
const ctx = useSdkContext();
|
|
40
|
+
try {
|
|
41
|
+
const { config: cfg, accessToken: token } = resolveHookArgs(config, accessToken, ctx);
|
|
42
|
+
return useMutation({
|
|
43
|
+
mutationFn: (request: PaymentInitiateRequest) =>
|
|
44
|
+
token ? initiatePayment(cfg, token, request) : Promise.reject('No access token'),
|
|
45
|
+
});
|
|
46
|
+
} catch (error) {
|
|
47
|
+
logger.warn('[useInitiatePayment] Called outside QueryClientProvider, returning no-op mutation');
|
|
48
|
+
return {
|
|
49
|
+
mutate: () => {},
|
|
50
|
+
mutateAsync: () => Promise.resolve(undefined),
|
|
51
|
+
} as any;
|
|
52
|
+
}
|
|
31
53
|
}
|
|
32
54
|
|
|
33
55
|
/**
|
|
34
56
|
* Hook for verifying a transaction
|
|
57
|
+
* Optional config — read from SdkContext if omitted.
|
|
35
58
|
*/
|
|
36
|
-
export function useVerifyTransaction() {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
59
|
+
export function useVerifyTransaction(config?: SdkConfig) {
|
|
60
|
+
const ctx = useSdkContext();
|
|
61
|
+
try {
|
|
62
|
+
const { config: cfg } = resolveHookArgs(config, undefined, ctx);
|
|
63
|
+
return useMutation({
|
|
64
|
+
mutationFn: (reference: string) => verifyTransaction(cfg, reference),
|
|
65
|
+
});
|
|
66
|
+
} catch (error) {
|
|
67
|
+
logger.warn('[useVerifyTransaction] Called outside QueryClientProvider, returning no-op mutation');
|
|
68
|
+
return {
|
|
69
|
+
mutate: () => {},
|
|
70
|
+
mutateAsync: () => Promise.resolve(undefined),
|
|
71
|
+
} as any;
|
|
72
|
+
}
|
|
41
73
|
}
|
|
42
74
|
|
|
43
75
|
/**
|
|
44
76
|
* Hook for getting transaction status
|
|
77
|
+
* Optional config — read from SdkContext if omitted.
|
|
45
78
|
*/
|
|
46
|
-
export function useTransactionStatus(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
79
|
+
export function useTransactionStatus(arg1?: SdkConfig | string, arg2?: string | boolean, arg3?: boolean) {
|
|
80
|
+
const ctx = useSdkContext();
|
|
81
|
+
const isArg1Config = typeof arg1 === 'object' && arg1 !== null && 'businessId' in arg1;
|
|
82
|
+
const explicitConfig = isArg1Config ? (arg1 as SdkConfig) : undefined;
|
|
83
|
+
const transactionId = isArg1Config ? (arg2 as string) : (typeof arg1 === 'string' ? arg1 : '');
|
|
84
|
+
const enabled = isArg1Config ? (arg3 ?? false) : (typeof arg2 === 'boolean' ? arg2 : false);
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const { config: cfg } = resolveHookArgs(explicitConfig, undefined, ctx);
|
|
88
|
+
return useQuery({
|
|
89
|
+
queryKey: ['transaction', 'status', transactionId],
|
|
90
|
+
queryFn: () => getTransactionStatus(cfg, transactionId),
|
|
91
|
+
enabled: enabled && !!transactionId,
|
|
92
|
+
refetchInterval: 3000,
|
|
93
|
+
});
|
|
94
|
+
} catch (error) {
|
|
95
|
+
logger.warn('[useTransactionStatus] Called outside QueryClientProvider, returning undefined');
|
|
96
|
+
return {
|
|
97
|
+
data: undefined,
|
|
98
|
+
isLoading: false,
|
|
99
|
+
error: null,
|
|
100
|
+
isFetching: false,
|
|
101
|
+
refetch: () => Promise.resolve({ data: undefined }),
|
|
102
|
+
} as any;
|
|
103
|
+
}
|
|
54
104
|
}
|
|
55
105
|
|
|
56
106
|
/**
|
|
57
107
|
* Hook for checking transaction status with automatic polling
|
|
108
|
+
* Optional config — read from SdkContext if omitted.
|
|
58
109
|
*/
|
|
59
|
-
export function usePollTransactionStatus(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
110
|
+
export function usePollTransactionStatus(arg1?: SdkConfig | string, arg2?: string | boolean, arg3?: boolean) {
|
|
111
|
+
const ctx = useSdkContext();
|
|
112
|
+
const isArg1Config = typeof arg1 === 'object' && arg1 !== null && 'businessId' in arg1;
|
|
113
|
+
const explicitConfig = isArg1Config ? (arg1 as SdkConfig) : undefined;
|
|
114
|
+
const reference = isArg1Config ? (arg2 as string) : (typeof arg1 === 'string' ? arg1 : '');
|
|
115
|
+
const enabled = isArg1Config ? (arg3 ?? false) : (typeof arg2 === 'boolean' ? arg2 : false);
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const { config: cfg } = resolveHookArgs(explicitConfig, undefined, ctx);
|
|
119
|
+
return useQuery({
|
|
120
|
+
queryKey: ['transaction', 'poll', reference],
|
|
121
|
+
queryFn: () => verifyTransaction(cfg, reference),
|
|
122
|
+
enabled: enabled && !!reference,
|
|
123
|
+
refetchInterval: 2000,
|
|
124
|
+
retry: 10,
|
|
125
|
+
});
|
|
126
|
+
} catch (error) {
|
|
127
|
+
logger.warn('[usePollTransactionStatus] Called outside QueryClientProvider, returning undefined');
|
|
128
|
+
return {
|
|
129
|
+
data: undefined,
|
|
130
|
+
isLoading: false,
|
|
131
|
+
error: null,
|
|
132
|
+
isFetching: false,
|
|
133
|
+
refetch: () => Promise.resolve({ data: undefined }),
|
|
134
|
+
} as any;
|
|
135
|
+
}
|
|
68
136
|
}
|
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Products Hooks
|
|
3
3
|
*
|
|
4
|
-
* React Query hooks for fetching products.
|
|
4
|
+
* React Query hooks for fetching products via Next.js API routes.
|
|
5
|
+
* Client-side hooks fetch from API routes to avoid exposing backend URLs to browser.
|
|
5
6
|
*/
|
|
6
7
|
import { useQuery } from '@tanstack/react-query'
|
|
7
|
-
import {
|
|
8
|
-
getStorefrontProducts,
|
|
9
|
-
getLatestProducts,
|
|
10
|
-
getPopularProducts,
|
|
11
|
-
getFeaturedProducts,
|
|
12
|
-
getNewProducts,
|
|
13
|
-
getStorefrontProductBySlug,
|
|
14
|
-
getRecommendedProducts
|
|
15
|
-
} from '../services/product.service'
|
|
16
8
|
import type { ListParams } from '../types/store'
|
|
17
9
|
import { getCacheTier } from '../utils/cache'
|
|
10
|
+
import { logger } from '../utils'
|
|
18
11
|
|
|
19
12
|
export const productKeys = {
|
|
20
13
|
all: ["products"] as const,
|
|
@@ -25,89 +18,294 @@ export const productKeys = {
|
|
|
25
18
|
popular: (params?: ListParams) => [...productKeys.all, "popular", params] as const,
|
|
26
19
|
featured: (params?: ListParams) => [...productKeys.all, "featured", params] as const,
|
|
27
20
|
new: (params?: ListParams) => [...productKeys.all, "new", params] as const,
|
|
21
|
+
onSale: (params?: ListParams) => [...productKeys.all, "onSale", params] as const,
|
|
22
|
+
lowStock: (params?: ListParams) => [...productKeys.all, "lowStock", params] as const,
|
|
28
23
|
}
|
|
29
24
|
|
|
30
25
|
/**
|
|
31
26
|
* Hook for fetching latest products.
|
|
27
|
+
* Fetches from Next.js API route to avoid exposing backend URLs.
|
|
32
28
|
*/
|
|
33
29
|
export function useLatestProducts(params?: ListParams) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
30
|
+
try {
|
|
31
|
+
const queryParams = new URLSearchParams()
|
|
32
|
+
if (params?.page) queryParams.set('page', params.page.toString())
|
|
33
|
+
if (params?.pageSize) queryParams.set('pageSize', params.pageSize.toString())
|
|
34
|
+
|
|
35
|
+
return useQuery({
|
|
36
|
+
queryKey: productKeys.latest(params),
|
|
37
|
+
queryFn: async () => {
|
|
38
|
+
const response = await fetch(`/api/products/latest?${queryParams.toString()}`)
|
|
39
|
+
if (!response.ok) throw new Error('Failed to fetch latest products')
|
|
40
|
+
return response.json()
|
|
41
|
+
},
|
|
42
|
+
// medium — latest products change infrequently
|
|
43
|
+
...getCacheTier('medium'),
|
|
44
|
+
})
|
|
45
|
+
} catch (error) {
|
|
46
|
+
logger.warn('[useLatestProducts] Called outside QueryClientProvider, returning empty list')
|
|
47
|
+
return {
|
|
48
|
+
data: undefined,
|
|
49
|
+
isLoading: false,
|
|
50
|
+
error: null,
|
|
51
|
+
isFetching: false,
|
|
52
|
+
refetch: () => Promise.resolve({ data: undefined }),
|
|
53
|
+
} as any
|
|
54
|
+
}
|
|
40
55
|
}
|
|
41
56
|
|
|
42
57
|
/**
|
|
43
58
|
* Hook for fetching popular products.
|
|
59
|
+
* Fetches from Next.js API route to avoid exposing backend URLs.
|
|
44
60
|
*/
|
|
45
61
|
export function usePopularProducts(params?: ListParams) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
62
|
+
try {
|
|
63
|
+
const queryParams = new URLSearchParams()
|
|
64
|
+
if (params?.page) queryParams.set('page', params.page.toString())
|
|
65
|
+
if (params?.pageSize) queryParams.set('pageSize', params.pageSize.toString())
|
|
66
|
+
|
|
67
|
+
return useQuery({
|
|
68
|
+
queryKey: productKeys.popular(params),
|
|
69
|
+
queryFn: async () => {
|
|
70
|
+
const response = await fetch(`/api/products/popular?${queryParams.toString()}`)
|
|
71
|
+
if (!response.ok) throw new Error('Failed to fetch popular products')
|
|
72
|
+
return response.json()
|
|
73
|
+
},
|
|
74
|
+
// medium — popular products change infrequently
|
|
75
|
+
...getCacheTier('medium'),
|
|
76
|
+
})
|
|
77
|
+
} catch (error) {
|
|
78
|
+
logger.warn('[usePopularProducts] Called outside QueryClientProvider, returning empty list')
|
|
79
|
+
return {
|
|
80
|
+
data: undefined,
|
|
81
|
+
isLoading: false,
|
|
82
|
+
error: null,
|
|
83
|
+
isFetching: false,
|
|
84
|
+
refetch: () => Promise.resolve({ data: undefined }),
|
|
85
|
+
} as any
|
|
86
|
+
}
|
|
52
87
|
}
|
|
53
88
|
|
|
54
89
|
/**
|
|
55
90
|
* Hook for fetching featured products.
|
|
91
|
+
* Fetches from Next.js API route to avoid exposing backend URLs.
|
|
56
92
|
*/
|
|
57
93
|
export function useFeaturedProducts(params?: ListParams) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
94
|
+
try {
|
|
95
|
+
const queryParams = new URLSearchParams()
|
|
96
|
+
if (params?.page) queryParams.set('page', params.page.toString())
|
|
97
|
+
if (params?.pageSize) queryParams.set('pageSize', params.pageSize.toString())
|
|
98
|
+
|
|
99
|
+
return useQuery({
|
|
100
|
+
queryKey: productKeys.featured(params),
|
|
101
|
+
queryFn: async () => {
|
|
102
|
+
const response = await fetch(`/api/products/featured?${queryParams.toString()}`)
|
|
103
|
+
if (!response.ok) throw new Error('Failed to fetch featured products')
|
|
104
|
+
return response.json()
|
|
105
|
+
},
|
|
106
|
+
// medium — featured products change infrequently
|
|
107
|
+
...getCacheTier('medium'),
|
|
108
|
+
})
|
|
109
|
+
} catch (error) {
|
|
110
|
+
logger.warn('[useFeaturedProducts] Called outside QueryClientProvider, returning empty list')
|
|
111
|
+
return {
|
|
112
|
+
data: undefined,
|
|
113
|
+
isLoading: false,
|
|
114
|
+
error: null,
|
|
115
|
+
isFetching: false,
|
|
116
|
+
refetch: () => Promise.resolve({ data: undefined }),
|
|
117
|
+
} as any
|
|
118
|
+
}
|
|
64
119
|
}
|
|
65
120
|
|
|
66
121
|
/**
|
|
67
122
|
* Hook for fetching new products.
|
|
123
|
+
* Fetches from Next.js API route to avoid exposing backend URLs.
|
|
68
124
|
*/
|
|
69
125
|
export function useNewProducts(params?: ListParams) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
126
|
+
try {
|
|
127
|
+
const queryParams = new URLSearchParams()
|
|
128
|
+
if (params?.page) queryParams.set('page', params.page.toString())
|
|
129
|
+
if (params?.pageSize) queryParams.set('pageSize', params.pageSize.toString())
|
|
130
|
+
|
|
131
|
+
return useQuery({
|
|
132
|
+
queryKey: productKeys.new(params),
|
|
133
|
+
queryFn: async () => {
|
|
134
|
+
const response = await fetch(`/api/products/new?${queryParams.toString()}`)
|
|
135
|
+
if (!response.ok) throw new Error('Failed to fetch new products')
|
|
136
|
+
return response.json()
|
|
137
|
+
},
|
|
138
|
+
// medium — new products change infrequently
|
|
139
|
+
...getCacheTier('medium'),
|
|
140
|
+
})
|
|
141
|
+
} catch (error) {
|
|
142
|
+
logger.warn('[useNewProducts] Called outside QueryClientProvider, returning empty list')
|
|
143
|
+
return {
|
|
144
|
+
data: undefined,
|
|
145
|
+
isLoading: false,
|
|
146
|
+
error: null,
|
|
147
|
+
isFetching: false,
|
|
148
|
+
refetch: () => Promise.resolve({ data: undefined }),
|
|
149
|
+
} as any
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Hook for fetching products currently on sale.
|
|
155
|
+
* Fetches from Next.js API route to avoid exposing backend URLs.
|
|
156
|
+
*/
|
|
157
|
+
export function useOnSaleProducts(params?: ListParams) {
|
|
158
|
+
try {
|
|
159
|
+
const queryParams = new URLSearchParams()
|
|
160
|
+
if (params?.page) queryParams.set('page', params.page.toString())
|
|
161
|
+
if (params?.pageSize) queryParams.set('pageSize', params.pageSize.toString())
|
|
162
|
+
|
|
163
|
+
return useQuery({
|
|
164
|
+
queryKey: productKeys.onSale(params),
|
|
165
|
+
queryFn: async () => {
|
|
166
|
+
const response = await fetch(`/api/products/on-sale?${queryParams.toString()}`)
|
|
167
|
+
if (!response.ok) throw new Error('Failed to fetch on-sale products')
|
|
168
|
+
return response.json()
|
|
169
|
+
},
|
|
170
|
+
// medium — sale products change infrequently
|
|
171
|
+
...getCacheTier('medium'),
|
|
172
|
+
})
|
|
173
|
+
} catch (error) {
|
|
174
|
+
logger.warn('[useOnSaleProducts] Called outside QueryClientProvider, returning empty list')
|
|
175
|
+
return {
|
|
176
|
+
data: undefined,
|
|
177
|
+
isLoading: false,
|
|
178
|
+
error: null,
|
|
179
|
+
isFetching: false,
|
|
180
|
+
refetch: () => Promise.resolve({ data: undefined }),
|
|
181
|
+
} as any
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Hook for fetching products with low stock.
|
|
187
|
+
* Fetches from Next.js API route to avoid exposing backend URLs.
|
|
188
|
+
*/
|
|
189
|
+
export function useLowStockProducts(params?: ListParams) {
|
|
190
|
+
try {
|
|
191
|
+
const queryParams = new URLSearchParams()
|
|
192
|
+
if (params?.page) queryParams.set('page', params.page.toString())
|
|
193
|
+
if (params?.pageSize) queryParams.set('pageSize', params.pageSize.toString())
|
|
194
|
+
|
|
195
|
+
return useQuery({
|
|
196
|
+
queryKey: productKeys.lowStock(params),
|
|
197
|
+
queryFn: async () => {
|
|
198
|
+
const response = await fetch(`/api/products/low-stock?${queryParams.toString()}`)
|
|
199
|
+
if (!response.ok) throw new Error('Failed to fetch low-stock products')
|
|
200
|
+
return response.json()
|
|
201
|
+
},
|
|
202
|
+
// low — low stock status changes frequently
|
|
203
|
+
...getCacheTier('low'),
|
|
204
|
+
})
|
|
205
|
+
} catch (error) {
|
|
206
|
+
logger.warn('[useLowStockProducts] Called outside QueryClientProvider, returning empty list')
|
|
207
|
+
return {
|
|
208
|
+
data: undefined,
|
|
209
|
+
isLoading: false,
|
|
210
|
+
error: null,
|
|
211
|
+
isFetching: false,
|
|
212
|
+
refetch: () => Promise.resolve({ data: undefined }),
|
|
213
|
+
} as any
|
|
214
|
+
}
|
|
76
215
|
}
|
|
77
216
|
|
|
78
217
|
/**
|
|
79
218
|
* Hook for fetching all active storefront products.
|
|
219
|
+
* Fetches from Next.js API route to avoid exposing backend URLs.
|
|
80
220
|
*/
|
|
81
221
|
export function useStorefrontProducts(params?: ListParams) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
222
|
+
try {
|
|
223
|
+
const queryParams = new URLSearchParams()
|
|
224
|
+
if (params?.page) queryParams.set('page', params.page.toString())
|
|
225
|
+
if (params?.pageSize) queryParams.set('pageSize', params.pageSize.toString())
|
|
226
|
+
if (params?.collectionSlug) queryParams.set('collectionSlug', params.collectionSlug)
|
|
227
|
+
if (params?.search) queryParams.set('search', params.search)
|
|
228
|
+
|
|
229
|
+
return useQuery({
|
|
230
|
+
queryKey: productKeys.list(params),
|
|
231
|
+
queryFn: async () => {
|
|
232
|
+
const response = await fetch(`/api/products?${queryParams.toString()}`)
|
|
233
|
+
if (!response.ok) throw new Error('Failed to fetch products')
|
|
234
|
+
return response.json()
|
|
235
|
+
},
|
|
236
|
+
// medium — product catalog changes infrequently
|
|
237
|
+
...getCacheTier('medium'),
|
|
238
|
+
})
|
|
239
|
+
} catch (error) {
|
|
240
|
+
logger.warn('[useStorefrontProducts] Called outside QueryClientProvider, returning empty list')
|
|
241
|
+
return {
|
|
242
|
+
data: undefined,
|
|
243
|
+
isLoading: false,
|
|
244
|
+
error: null,
|
|
245
|
+
isFetching: false,
|
|
246
|
+
refetch: () => Promise.resolve({ data: undefined }),
|
|
247
|
+
} as any
|
|
248
|
+
}
|
|
88
249
|
}
|
|
89
250
|
|
|
90
251
|
/**
|
|
91
252
|
* Hook for fetching a single product by slug.
|
|
253
|
+
* Fetches from Next.js API route to avoid exposing backend URLs.
|
|
92
254
|
*/
|
|
93
255
|
export function useProduct(slug: string) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
256
|
+
try {
|
|
257
|
+
return useQuery({
|
|
258
|
+
queryKey: productKeys.detail(slug),
|
|
259
|
+
queryFn: async () => {
|
|
260
|
+
const response = await fetch(`/api/products/${slug}`)
|
|
261
|
+
if (!response.ok) throw new Error('Failed to fetch product')
|
|
262
|
+
return response.json()
|
|
263
|
+
},
|
|
264
|
+
// medium — product details change infrequently
|
|
265
|
+
...getCacheTier('medium'),
|
|
266
|
+
enabled: !!slug,
|
|
267
|
+
})
|
|
268
|
+
} catch (error) {
|
|
269
|
+
logger.warn('[useProduct] Called outside QueryClientProvider, returning undefined')
|
|
270
|
+
return {
|
|
271
|
+
data: undefined,
|
|
272
|
+
isLoading: false,
|
|
273
|
+
error: null,
|
|
274
|
+
isFetching: false,
|
|
275
|
+
refetch: () => Promise.resolve({ data: undefined }),
|
|
276
|
+
} as any
|
|
277
|
+
}
|
|
101
278
|
}
|
|
102
279
|
|
|
103
280
|
/**
|
|
104
281
|
* Hook for fetching recommended products.
|
|
282
|
+
* Fetches from Next.js API route to avoid exposing backend URLs.
|
|
105
283
|
*/
|
|
106
284
|
export function useRecommendedProducts(params?: ListParams) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
285
|
+
try {
|
|
286
|
+
const queryParams = new URLSearchParams()
|
|
287
|
+
if (params?.page) queryParams.set('page', params.page.toString())
|
|
288
|
+
if (params?.pageSize) queryParams.set('pageSize', params.pageSize.toString())
|
|
289
|
+
if (params?.excludeProductId) queryParams.set('excludeProductId', params.excludeProductId)
|
|
290
|
+
|
|
291
|
+
return useQuery({
|
|
292
|
+
queryKey: productKeys.recommended(params),
|
|
293
|
+
queryFn: async () => {
|
|
294
|
+
const response = await fetch(`/api/products/recommended?${queryParams.toString()}`)
|
|
295
|
+
if (!response.ok) throw new Error('Failed to fetch recommended products')
|
|
296
|
+
return response.json()
|
|
297
|
+
},
|
|
298
|
+
// medium — recommendations change infrequently
|
|
299
|
+
...getCacheTier('medium'),
|
|
300
|
+
})
|
|
301
|
+
} catch (error) {
|
|
302
|
+
logger.warn('[useRecommendedProducts] Called outside QueryClientProvider, returning empty list')
|
|
303
|
+
return {
|
|
304
|
+
data: undefined,
|
|
305
|
+
isLoading: false,
|
|
306
|
+
error: null,
|
|
307
|
+
isFetching: false,
|
|
308
|
+
refetch: () => Promise.resolve({ data: undefined }),
|
|
309
|
+
} as any
|
|
310
|
+
}
|
|
113
311
|
}
|