@instockng/api-client 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.
@@ -0,0 +1,298 @@
1
+ /**
2
+ * Type-safe RPC hooks for cart operations
3
+ *
4
+ * These hooks use Hono RPC client with types directly from the backend,
5
+ * providing end-to-end type safety without code generation.
6
+ */
7
+
8
+ import { useMutation, useQueryClient, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
9
+ import { useQueryUnwrapped } from '../use-query-unwrapped';
10
+ import { queryKeys } from '../../utils/query-keys';
11
+ import {
12
+ fetchCart,
13
+ updateCart,
14
+ createCart,
15
+ addCartItem,
16
+ updateCartItem,
17
+ removeCartItem,
18
+ applyDiscount,
19
+ removeDiscount,
20
+ checkoutCart,
21
+ } from '../../fetchers/carts';
22
+
23
+ /**
24
+ * Hook to get cart by ID using RPC
25
+ *
26
+ * @param cartId - Cart UUID
27
+ * @param options - React Query options
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * const { data: cart, isLoading } = useGetCart('cart-123');
32
+ * console.log(cart?.cart.brand.name); // Fully typed!
33
+ * ```
34
+ */
35
+ export function useGetCart(
36
+ cartId: string,
37
+ options?: Omit<UseQueryOptions<Awaited<ReturnType<typeof fetchCart>>, Error>, 'queryKey' | 'queryFn'>
38
+ ) {
39
+ return useQueryUnwrapped({
40
+ queryKey: queryKeys.public.carts.detail(cartId),
41
+ queryFn: () => fetchCart(cartId),
42
+ ...options,
43
+ });
44
+ }
45
+
46
+ /**
47
+ * Hook to update cart using RPC
48
+ *
49
+ * @param cartId - Cart UUID
50
+ * @param options - React Query mutation options
51
+ *
52
+ * @example
53
+ * ```tsx
54
+ * const updateCart = useUpdateCart('cart-123');
55
+ * updateCart.mutate({
56
+ * customerEmail: 'user@example.com',
57
+ * deliveryZoneId: 'zone-123'
58
+ * });
59
+ * ```
60
+ */
61
+ export function useUpdateCart(
62
+ cartId: string,
63
+ options?: UseMutationOptions<
64
+ Awaited<ReturnType<typeof updateCart>>,
65
+ Error,
66
+ Parameters<typeof updateCart>[1]
67
+ >
68
+ ) {
69
+ const queryClient = useQueryClient();
70
+
71
+ return useMutation({
72
+ mutationFn: (data) => updateCart(cartId, data),
73
+ onSuccess: () => {
74
+ queryClient.invalidateQueries({ queryKey: queryKeys.public.carts.detail(cartId) });
75
+ },
76
+ ...options,
77
+ });
78
+ }
79
+
80
+ /**
81
+ * Hook to create a new cart using RPC
82
+ *
83
+ * @param options - React Query mutation options
84
+ *
85
+ * @example
86
+ * ```tsx
87
+ * const createCart = useCreateCart();
88
+ * createCart.mutate('my-brand');
89
+ * ```
90
+ */
91
+ export function useCreateCart(
92
+ options?: UseMutationOptions<
93
+ Awaited<ReturnType<typeof createCart>>,
94
+ Error,
95
+ string
96
+ >
97
+ ) {
98
+ return useMutation({
99
+ mutationFn: (brandSlug: string) => createCart(brandSlug),
100
+ ...options,
101
+ });
102
+ }
103
+
104
+ /**
105
+ * Hook to apply discount code to cart using RPC
106
+ *
107
+ * @param cartId - Cart UUID
108
+ * @param options - React Query mutation options
109
+ *
110
+ * @example
111
+ * ```tsx
112
+ * const applyDiscount = useApplyDiscount('cart-123');
113
+ * applyDiscount.mutate({ code: 'SAVE10' });
114
+ * ```
115
+ */
116
+ export function useApplyDiscount(
117
+ cartId: string,
118
+ options?: UseMutationOptions<
119
+ Awaited<ReturnType<typeof applyDiscount>>,
120
+ Error,
121
+ { code: string }
122
+ >
123
+ ) {
124
+ const queryClient = useQueryClient();
125
+
126
+ return useMutation({
127
+ mutationFn: (data) => applyDiscount(cartId, data.code),
128
+ onSuccess: () => {
129
+ queryClient.invalidateQueries({ queryKey: queryKeys.public.carts.detail(cartId) });
130
+ },
131
+ ...options,
132
+ });
133
+ }
134
+
135
+ /**
136
+ * Hook to remove discount code from cart using RPC
137
+ *
138
+ * @param cartId - Cart UUID
139
+ * @param options - React Query mutation options
140
+ *
141
+ * @example
142
+ * ```tsx
143
+ * const removeDiscount = useRemoveDiscount('cart-123');
144
+ * removeDiscount.mutate();
145
+ * ```
146
+ */
147
+ export function useRemoveDiscount(
148
+ cartId: string,
149
+ options?: UseMutationOptions<
150
+ Awaited<ReturnType<typeof removeDiscount>>,
151
+ Error,
152
+ void
153
+ >
154
+ ) {
155
+ const queryClient = useQueryClient();
156
+
157
+ return useMutation({
158
+ mutationFn: () => removeDiscount(cartId),
159
+ onSuccess: () => {
160
+ queryClient.invalidateQueries({ queryKey: queryKeys.public.carts.detail(cartId) });
161
+ },
162
+ ...options,
163
+ });
164
+ }
165
+
166
+ /**
167
+ * Hook to add item to cart using RPC
168
+ *
169
+ * @param cartId - Cart UUID
170
+ * @param options - React Query mutation options
171
+ *
172
+ * @example
173
+ * ```tsx
174
+ * const addItem = useAddCartItem('cart-123');
175
+ * addItem.mutate({ sku: 'PROD-001', quantity: 2 });
176
+ * ```
177
+ */
178
+ export function useAddCartItem(
179
+ cartId: string,
180
+ options?: UseMutationOptions<
181
+ Awaited<ReturnType<typeof addCartItem>>,
182
+ Error,
183
+ { sku: string; quantity: number }
184
+ >
185
+ ) {
186
+ const queryClient = useQueryClient();
187
+
188
+ return useMutation({
189
+ mutationFn: (data) => addCartItem(cartId, data.sku, data.quantity),
190
+ onSuccess: () => {
191
+ queryClient.invalidateQueries({ queryKey: queryKeys.public.carts.detail(cartId) });
192
+ },
193
+ ...options,
194
+ });
195
+ }
196
+
197
+ /**
198
+ * Hook to update cart item quantity using RPC
199
+ *
200
+ * @param cartId - Cart UUID
201
+ * @param options - React Query mutation options
202
+ *
203
+ * @example
204
+ * ```tsx
205
+ * const updateItem = useUpdateCartItem('cart-123');
206
+ * updateItem.mutate({ itemId: 'item-456', quantity: 3 });
207
+ * ```
208
+ */
209
+ export function useUpdateCartItem(
210
+ cartId: string,
211
+ options?: UseMutationOptions<
212
+ Awaited<ReturnType<typeof updateCartItem>>,
213
+ Error,
214
+ { itemId: string; quantity: number }
215
+ >
216
+ ) {
217
+ const queryClient = useQueryClient();
218
+
219
+ return useMutation({
220
+ mutationFn: ({ itemId, quantity }) => updateCartItem(cartId, itemId, quantity),
221
+ onSuccess: () => {
222
+ queryClient.invalidateQueries({ queryKey: queryKeys.public.carts.detail(cartId) });
223
+ },
224
+ ...options,
225
+ });
226
+ }
227
+
228
+ /**
229
+ * Hook to remove item from cart using RPC
230
+ *
231
+ * @param cartId - Cart UUID
232
+ * @param options - React Query mutation options
233
+ *
234
+ * @example
235
+ * ```tsx
236
+ * const removeItem = useRemoveCartItem('cart-123');
237
+ * removeItem.mutate('item-456'); // Pass itemId in mutate call
238
+ * ```
239
+ */
240
+ export function useRemoveCartItem(
241
+ cartId: string,
242
+ options?: UseMutationOptions<
243
+ Awaited<ReturnType<typeof removeCartItem>>,
244
+ Error,
245
+ string
246
+ >
247
+ ) {
248
+ const queryClient = useQueryClient();
249
+
250
+ return useMutation({
251
+ mutationFn: (itemId: string) => removeCartItem(cartId, itemId),
252
+ onSuccess: () => {
253
+ queryClient.invalidateQueries({ queryKey: queryKeys.public.carts.detail(cartId) });
254
+ },
255
+ ...options,
256
+ });
257
+ }
258
+
259
+ /**
260
+ * Hook to checkout a cart and create an order using RPC
261
+ *
262
+ * @param cartId - Cart UUID
263
+ * @param options - React Query mutation options
264
+ *
265
+ * @example
266
+ * ```tsx
267
+ * const checkout = useCheckoutCart('cart-123');
268
+ * checkout.mutate({
269
+ * firstName: 'John',
270
+ * lastName: 'Doe',
271
+ * email: 'john@example.com',
272
+ * phone: '+1234567890',
273
+ * address: '123 Main St',
274
+ * city: 'New York',
275
+ * deliveryZoneId: 'zone-123',
276
+ * paymentMethod: 'cod'
277
+ * });
278
+ * ```
279
+ */
280
+ export function useCheckoutCart(
281
+ cartId: string,
282
+ options?: UseMutationOptions<
283
+ Awaited<ReturnType<typeof checkoutCart>>,
284
+ Error,
285
+ Parameters<typeof checkoutCart>[1]
286
+ >
287
+ ) {
288
+ const queryClient = useQueryClient();
289
+
290
+ return useMutation({
291
+ mutationFn: (data) => checkoutCart(cartId, data),
292
+ onSuccess: () => {
293
+ // Invalidate cart query since it's now converted to an order
294
+ queryClient.invalidateQueries({ queryKey: queryKeys.public.carts.detail(cartId) });
295
+ },
296
+ ...options,
297
+ });
298
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Type-safe RPC hooks for delivery zone operations
3
+ *
4
+ * These hooks use Hono RPC client with types directly from the backend,
5
+ * providing end-to-end type safety without code generation.
6
+ */
7
+
8
+ import { UseQueryOptions } from '@tanstack/react-query';
9
+ import { useQueryUnwrapped } from '../use-query-unwrapped';
10
+ import { queryKeys } from '../../utils/query-keys';
11
+ import { fetchDeliveryZones } from '../../fetchers/delivery-zones';
12
+
13
+ /**
14
+ * Hook to get delivery zones using RPC
15
+ *
16
+ * @param brandId - Optional brand UUID to filter brand-specific zones
17
+ * @param options - React Query options
18
+ *
19
+ * @example
20
+ * ```tsx
21
+ * const { data: zones, isLoading } = useGetDeliveryZones(undefined);
22
+ * console.log(zones?.data[0].name); // Fully typed!
23
+ * ```
24
+ */
25
+ export function useGetDeliveryZones(
26
+ brandId: string | undefined,
27
+ options?: Omit<UseQueryOptions<Awaited<ReturnType<typeof fetchDeliveryZones>>, Error>, 'queryKey' | 'queryFn'>
28
+ ) {
29
+ return useQueryUnwrapped({
30
+ queryKey: queryKeys.public.deliveryZones.list(brandId),
31
+ queryFn: () => fetchDeliveryZones(brandId),
32
+ ...options,
33
+ });
34
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Type-safe RPC hooks
3
+ *
4
+ * Export all RPC hooks for use in applications
5
+ */
6
+
7
+ export * from './carts';
8
+ export * from './orders';
9
+ export * from './products';
10
+ export * from './delivery-zones';
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Type-safe RPC hooks for order operations
3
+ *
4
+ * These hooks use Hono RPC client with types directly from the backend,
5
+ * providing end-to-end type safety without code generation.
6
+ */
7
+
8
+ import { useMutation, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
9
+ import { useQueryUnwrapped } from '../use-query-unwrapped';
10
+ import { queryKeys } from '../../utils/query-keys';
11
+ import { fetchOrder, confirmOrder } from '../../fetchers/orders';
12
+
13
+ /**
14
+ * Hook to get order by ID and token using RPC
15
+ *
16
+ * @param orderId - Order UUID
17
+ * @param token - User action token
18
+ * @param options - React Query options
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * const { data: order, isLoading } = useGetOrder('order-123', 'token-456');
23
+ * console.log(order?.order.status); // Fully typed!
24
+ * ```
25
+ */
26
+ export function useGetOrder(
27
+ orderId: string,
28
+ token: string,
29
+ options?: Omit<UseQueryOptions<Awaited<ReturnType<typeof fetchOrder>>, Error>, 'queryKey' | 'queryFn'>
30
+ ) {
31
+ return useQueryUnwrapped({
32
+ queryKey: queryKeys.public.orders.detail(orderId, token),
33
+ queryFn: () => fetchOrder(orderId, token),
34
+ ...options,
35
+ });
36
+ }
37
+
38
+ /**
39
+ * Hook to confirm a prospect order using RPC
40
+ *
41
+ * @param options - React Query mutation options
42
+ *
43
+ * @example
44
+ * ```tsx
45
+ * const confirmOrder = useConfirmOrder();
46
+ * confirmOrder.mutate({
47
+ * orderId: 'order-123',
48
+ * token: 'token-456'
49
+ * });
50
+ * ```
51
+ */
52
+ export function useConfirmOrder(
53
+ options?: UseMutationOptions<
54
+ Awaited<ReturnType<typeof confirmOrder>>,
55
+ Error,
56
+ {
57
+ orderId: string;
58
+ token: string;
59
+ }
60
+ >
61
+ ) {
62
+ return useMutation({
63
+ mutationFn: (data) => confirmOrder(data.orderId, data.token),
64
+ ...options,
65
+ });
66
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Type-safe RPC hooks for product operations
3
+ *
4
+ * These hooks use Hono RPC client with types directly from the backend,
5
+ * providing end-to-end type safety without code generation.
6
+ */
7
+
8
+ import { UseQueryOptions } from '@tanstack/react-query';
9
+ import { useQueryUnwrapped } from '../use-query-unwrapped';
10
+ import { queryKeys } from '../../utils/query-keys';
11
+ import { fetchProductsByBrand, fetchProductBySlug } from '../../fetchers/products';
12
+
13
+ /**
14
+ * Hook to get products by brand using RPC
15
+ *
16
+ * @param brandId - Brand UUID
17
+ * @param options - React Query options
18
+ *
19
+ * @example
20
+ * ```tsx
21
+ * const { data: products, isLoading } = useGetProducts('brand-123');
22
+ * console.log(products?.data[0].name); // Fully typed!
23
+ * ```
24
+ */
25
+ export function useGetProducts(
26
+ brandId: string,
27
+ options?: Omit<UseQueryOptions<Awaited<ReturnType<typeof fetchProductsByBrand>>, Error>, 'queryKey' | 'queryFn'>
28
+ ) {
29
+ return useQueryUnwrapped({
30
+ queryKey: queryKeys.public.products.list(brandId),
31
+ queryFn: () => fetchProductsByBrand(brandId),
32
+ ...options,
33
+ });
34
+ }
35
+
36
+ /**
37
+ * Hook to get a single product by slug using RPC
38
+ *
39
+ * @param slug - Product slug
40
+ * @param options - React Query options
41
+ *
42
+ * @example
43
+ * ```tsx
44
+ * const { data: product, isLoading } = useGetProduct('cotton-t-shirt');
45
+ * console.log(product?.name); // Fully typed!
46
+ * ```
47
+ */
48
+ export function useGetProduct(
49
+ slug: string,
50
+ options?: Omit<UseQueryOptions<Awaited<ReturnType<typeof fetchProductBySlug>>, Error>, 'queryKey' | 'queryFn'>
51
+ ) {
52
+ return useQueryUnwrapped({
53
+ queryKey: queryKeys.public.products.detail(slug),
54
+ queryFn: () => fetchProductBySlug(slug),
55
+ ...options,
56
+ });
57
+ }
@@ -0,0 +1,30 @@
1
+ import { useQuery, type UseQueryOptions, type UseQueryResult } from '@tanstack/react-query';
2
+
3
+ /**
4
+ * Wrapper around useQuery that narrows the data type to exclude error responses
5
+ *
6
+ * Since our hooks throw on HTTP errors (via `if (!res.ok) throw new Error(...)`),
7
+ * the data will never actually be an error response at runtime. This wrapper
8
+ * tells TypeScript that truth.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * // Instead of:
13
+ * return useQuery({ ... }) // Returns UseQueryResult<Data | { error: ... }, Error>
14
+ *
15
+ * // Use:
16
+ * return useQueryUnwrapped({ ... }) // Returns UseQueryResult<Data, Error>
17
+ * ```
18
+ */
19
+ export function useQueryUnwrapped<
20
+ TData,
21
+ TError = Error,
22
+ TQueryKey extends readonly unknown[] = readonly unknown[]
23
+ >(
24
+ options: UseQueryOptions<TData, TError, TData, TQueryKey>
25
+ ): UseQueryResult<Exclude<TData, { error: any }>, TError> {
26
+ const result = useQuery(options);
27
+
28
+ // Cast the data to exclude error types since we throw on errors
29
+ return result as UseQueryResult<Exclude<TData, { error: any }>, TError>;
30
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Hook to access API configuration from context
3
+ */
4
+
5
+ import { useApiClientContext } from '../provider';
6
+
7
+ export interface ApiConfig {
8
+ baseURL: string;
9
+ authToken: string;
10
+ }
11
+
12
+ /**
13
+ * Get API configuration from context
14
+ */
15
+ export function useApiConfig(): ApiConfig {
16
+ const { baseURL, authToken } = useApiClientContext();
17
+
18
+ return {
19
+ baseURL,
20
+ authToken: authToken || '',
21
+ };
22
+ }
package/src/index.ts ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @oms/api-client
3
+ *
4
+ * Type-safe React Query hooks for OMS API using Hono RPC
5
+ */
6
+
7
+ // Export provider
8
+ export { ApiClientProvider, useApiClientContext } from './provider';
9
+ export type { ApiClientProviderProps } from './provider';
10
+
11
+ // Export API config hook
12
+ export { useApiConfig } from './hooks/useApiConfig';
13
+ export type { ApiConfig } from './hooks/useApiConfig';
14
+
15
+ // Export client utilities (legacy axios client - deprecated)
16
+ export { getApiClient, initializeApiClient, resetApiClient } from './client';
17
+ export type { ApiClientConfig } from './client';
18
+
19
+ // Export RPC client utilities (type-safe Hono RPC)
20
+ export { createRpcClients, createAdminRpcClients, authHeaders } from './rpc-client';
21
+ export type { RpcClients, AdminRpcClients } from './rpc-client';
22
+
23
+ // Export fetchers for Server Components (can be used without hooks)
24
+ export * from './fetchers';
25
+
26
+ // Export query keys for advanced usage
27
+ export { queryKeys } from './utils/query-keys';
28
+
29
+ // Export all public hooks directly from main package
30
+ // These can be imported as: import { useGetCart } from '@oms/api-client'
31
+ export * from './hooks/public';
32
+
33
+ // Export namespaced versions for backward compatibility
34
+ // These can be imported as: import { publicApi, adminApi } from '@oms/api-client'
35
+ import * as publicHooks from './hooks/public';
36
+ import * as adminHooks from './hooks/admin';
37
+
38
+ export { publicHooks as publicApi, adminHooks as adminApi };
39
+
40
+ // Export all type definitions
41
+ // These can be imported as: import type { Brand, Product } from '@oms/api-client'
42
+ export type * from './types';
@@ -0,0 +1,89 @@
1
+ /**
2
+ * React context provider for API client configuration
3
+ */
4
+
5
+ import { createContext, useContext, useEffect, ReactNode, useMemo } from 'react';
6
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
7
+ import { AxiosError } from 'axios';
8
+ import { initializeApiClient, ApiClientConfig } from './client';
9
+
10
+ interface ApiClientContextValue {
11
+ baseURL: string;
12
+ authToken?: string;
13
+ }
14
+
15
+ const ApiClientContext = createContext<ApiClientContextValue | null>(null);
16
+
17
+ export interface ApiClientProviderProps {
18
+ children: ReactNode;
19
+ baseURL?: string;
20
+ authToken?: string;
21
+ onError?: (error: AxiosError) => void;
22
+ queryClient?: QueryClient;
23
+ }
24
+
25
+ /**
26
+ * Provider component that initializes the API client and React Query
27
+ *
28
+ * @example
29
+ * ```tsx
30
+ * <ApiClientProvider>
31
+ * <App />
32
+ * </ApiClientProvider>
33
+ * ```
34
+ */
35
+ export function ApiClientProvider({
36
+ children,
37
+ baseURL = 'https://oms-api.instock.ng',
38
+ authToken,
39
+ onError,
40
+ queryClient: externalQueryClient
41
+ }: ApiClientProviderProps) {
42
+ // Initialize client synchronously on first render
43
+ const config: ApiClientConfig = {
44
+ baseURL,
45
+ onError,
46
+ };
47
+
48
+ initializeApiClient(config);
49
+
50
+ // Update client when config changes
51
+ useEffect(() => {
52
+ initializeApiClient(config);
53
+ }, [baseURL, onError]);
54
+
55
+ // Create a default query client if one isn't provided
56
+ const queryClient = useMemo(
57
+ () =>
58
+ externalQueryClient ||
59
+ new QueryClient({
60
+ defaultOptions: {
61
+ queries: {
62
+ retry: 1,
63
+ refetchOnWindowFocus: false,
64
+ staleTime: 5000,
65
+ },
66
+ },
67
+ }),
68
+ [externalQueryClient]
69
+ );
70
+
71
+ return (
72
+ <QueryClientProvider client={queryClient}>
73
+ <ApiClientContext.Provider value={{ baseURL, authToken }}>
74
+ {children}
75
+ </ApiClientContext.Provider>
76
+ </QueryClientProvider>
77
+ );
78
+ }
79
+
80
+ /**
81
+ * Hook to access API client context
82
+ */
83
+ export function useApiClientContext() {
84
+ const context = useContext(ApiClientContext);
85
+ if (!context) {
86
+ throw new Error('useApiClientContext must be used within ApiClientProvider');
87
+ }
88
+ return context;
89
+ }