@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.
package/README.md ADDED
@@ -0,0 +1,263 @@
1
+ # @oms/api-client
2
+
3
+ React Query hooks for the OMS API. This package provides type-safe API client with hooks for both public (e-commerce) and admin (internal) endpoints.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @oms/api-client
9
+ # or
10
+ pnpm add @oms/api-client
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ### 1. Setup Provider
16
+
17
+ Wrap your app with `ApiClientProvider`:
18
+
19
+ ```tsx
20
+ import { ApiClientProvider } from '@oms/api-client';
21
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
22
+
23
+ const queryClient = new QueryClient();
24
+
25
+ function App() {
26
+ return (
27
+ <QueryClientProvider client={queryClient}>
28
+ <ApiClientProvider baseURL="https://api.yoursite.com">
29
+ <YourApp />
30
+ </ApiClientProvider>
31
+ </QueryClientProvider>
32
+ );
33
+ }
34
+ ```
35
+
36
+ ### 2. Use Hooks
37
+
38
+ #### Orders Example
39
+
40
+ ```tsx
41
+ import { useGetOrder, useConfirmOrder } from '@oms/api-client';
42
+
43
+ function OrderConfirmation() {
44
+ const { orderId, token } = useParams();
45
+
46
+ // Get order details
47
+ const { data, isLoading } = useGetOrder(orderId, token);
48
+
49
+ // Confirm order
50
+ const confirmMutation = useConfirmOrder({
51
+ onSuccess: () => {
52
+ console.log('Order confirmed!');
53
+ }
54
+ });
55
+
56
+ return (
57
+ <div>
58
+ <h1>Order #{data?.order.orderNumber}</h1>
59
+ <button onClick={() => confirmMutation.mutate({ orderId, token })}>
60
+ Confirm Order
61
+ </button>
62
+ </div>
63
+ );
64
+ }
65
+ ```
66
+
67
+ #### Products Example
68
+
69
+ ```tsx
70
+ import { useGetProducts } from '@oms/api-client';
71
+
72
+ function ProductList({ brandId }) {
73
+ const { data, isLoading } = useGetProducts(brandId);
74
+
75
+ if (isLoading) return <div>Loading...</div>;
76
+
77
+ return (
78
+ <div>
79
+ {data?.products.map(product => (
80
+ <div key={product.id}>
81
+ <h3>{product.name}</h3>
82
+ <p>{product.description}</p>
83
+ </div>
84
+ ))}
85
+ </div>
86
+ );
87
+ }
88
+ ```
89
+
90
+ #### Cart & Checkout Example
91
+
92
+ ```tsx
93
+ import {
94
+ useGetCart,
95
+ useCreateCart,
96
+ useAddToCart,
97
+ useRemoveCartItem,
98
+ useCheckout
99
+ } from '@oms/api-client';
100
+
101
+ function ShoppingCart() {
102
+ const [cartId, setCartId] = useState<string | null>(null);
103
+ const { data: cart } = useGetCart(cartId!, { enabled: !!cartId });
104
+
105
+ const createCart = useCreateCart({
106
+ onSuccess: (data) => setCartId(data.cart.id)
107
+ });
108
+
109
+ const addToCart = useAddToCart(cartId!, {
110
+ onSuccess: () => {
111
+ // Cart is automatically updated via React Query
112
+ }
113
+ });
114
+
115
+ const removeItem = useRemoveCartItem(cartId!, '');
116
+
117
+ const checkout = useCheckout(cartId!, {
118
+ onSuccess: (data) => {
119
+ console.log('Order created:', data.order.id);
120
+ }
121
+ });
122
+
123
+ const handleAddToCart = (sku: string, quantity: number) => {
124
+ if (!cartId) {
125
+ createCart.mutate({
126
+ brandId: 'your-brand-id',
127
+ expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString()
128
+ });
129
+ } else {
130
+ addToCart.mutate({ sku, quantity });
131
+ }
132
+ };
133
+
134
+ const handleCheckout = (customerInfo) => {
135
+ checkout.mutate({
136
+ ...customerInfo,
137
+ paymentMethod: 'cod'
138
+ });
139
+ };
140
+
141
+ return (
142
+ <div>
143
+ {cart?.items.map(item => (
144
+ <div key={item.id}>
145
+ <span>{item.variant.product.name} x {item.quantity}</span>
146
+ <button onClick={() => removeItem.mutate()}>Remove</button>
147
+ </div>
148
+ ))}
149
+ <button onClick={() => handleCheckout(formData)}>
150
+ Checkout
151
+ </button>
152
+ </div>
153
+ );
154
+ }
155
+ ```
156
+
157
+ #### Admin Hooks (Internal OMS)
158
+
159
+ ```tsx
160
+ // Coming soon - admin hooks will be added here
161
+ // import { useGetOrders, useUpdateOrder } from '@oms/api-client/admin';
162
+ ```
163
+
164
+ ### 3. Custom Error Handling
165
+
166
+ ```tsx
167
+ <ApiClientProvider
168
+ baseURL="https://api.yoursite.com"
169
+ onError={(error) => {
170
+ console.error('API Error:', error.message);
171
+ // Show toast notification, etc.
172
+ }}
173
+ >
174
+ <App />
175
+ </ApiClientProvider>
176
+ ```
177
+
178
+ ## Available Hooks
179
+
180
+ ### Public Hooks
181
+
182
+ #### Orders
183
+ - `useGetOrder(orderId, token, options?)` - Get order details by ID and token
184
+ - `useConfirmOrder(options?)` - Confirm a prospect order
185
+
186
+ #### Products
187
+ - `useGetProducts(brandId, options?)` - Get all products for a brand
188
+
189
+ #### Carts
190
+ - `useGetCart(cartId, options?)` - Get cart details by ID
191
+ - `useCreateCart(options?)` - Create a new cart
192
+ - `useAddToCart(cartId, options?)` - Add item to cart by SKU
193
+ - `useRemoveCartItem(cartId, itemId, options?)` - Remove item from cart
194
+ - `useApplyDiscount(cartId, options?)` - Apply discount code to cart
195
+ - `useRemoveDiscount(cartId, options?)` - Remove discount from cart
196
+ - `useCheckout(cartId, options?)` - Checkout cart and create order
197
+
198
+ #### Delivery Zones
199
+ - `useGetDeliveryZones(params?, options?)` - Get delivery zones with optional filters
200
+
201
+ ### Admin Hooks
202
+
203
+ Coming soon! Will include hooks for:
204
+ - Orders management
205
+ - Inventory management
206
+ - Products management
207
+ - Brands management
208
+
209
+ ## Type Safety
210
+
211
+ All hooks are fully typed using OpenAPI-generated types from `@oms/types`. TypeScript will autocomplete and validate all API requests and responses.
212
+
213
+ ## Advanced Usage
214
+
215
+ ### Query Keys
216
+
217
+ Access query keys for manual cache invalidation:
218
+
219
+ ```tsx
220
+ import { queryKeys } from '@oms/api-client';
221
+ import { useQueryClient } from '@tanstack/react-query';
222
+
223
+ function MyComponent() {
224
+ const queryClient = useQueryClient();
225
+
226
+ // Invalidate specific order
227
+ queryClient.invalidateQueries({
228
+ queryKey: queryKeys.public.orders.detail(orderId, token)
229
+ });
230
+
231
+ // Invalidate all orders
232
+ queryClient.invalidateQueries({
233
+ queryKey: queryKeys.public.orders.all
234
+ });
235
+ }
236
+ ```
237
+
238
+ ### Direct API Client Access
239
+
240
+ For advanced use cases, access the axios client directly:
241
+
242
+ ```tsx
243
+ import { getApiClient } from '@oms/api-client';
244
+
245
+ const client = getApiClient();
246
+ const response = await client.get('/custom-endpoint');
247
+ ```
248
+
249
+ ## Development
250
+
251
+ ### Updating Types
252
+
253
+ When the backend API changes, regenerate types:
254
+
255
+ ```bash
256
+ # From the monorepo root
257
+ cd packages/types
258
+ pnpm run generate
259
+ ```
260
+
261
+ ## License
262
+
263
+ ISC
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@instockng/api-client",
3
+ "version": "1.0.0",
4
+ "description": "React Query hooks for OMS API",
5
+ "main": "src/index.ts",
6
+ "types": "src/index.ts",
7
+ "exports": {
8
+ ".": "./src/index.ts",
9
+ "./fetchers": "./src/fetchers/index.ts",
10
+ "./public": "./src/hooks/public/index.ts",
11
+ "./admin": "./src/hooks/admin/index.ts"
12
+ },
13
+ "files": [
14
+ "src",
15
+ "README.md"
16
+ ],
17
+ "keywords": [
18
+ "api",
19
+ "react-query",
20
+ "hooks",
21
+ "oms"
22
+ ],
23
+ "author": "",
24
+ "license": "ISC",
25
+ "dependencies": {
26
+ "@trpc/client": "^11.6.0",
27
+ "hono": "^4.9.10"
28
+ },
29
+ "peerDependencies": {
30
+ "@tanstack/react-query": "^5.0.0",
31
+ "axios": "^1.6.0",
32
+ "react": "^18.0.0 || ^19.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "@tanstack/react-query": "^5.17.19",
36
+ "@types/react": "npm:types-react@^19.0.0-rc.1",
37
+ "axios": "^1.6.5",
38
+ "react": "^19.0.0",
39
+ "typescript": "^5.3.3"
40
+ },
41
+ "scripts": {
42
+ "type-check": "tsc --noEmit",
43
+ "build": "tsc --build"
44
+ }
45
+ }
package/src/client.ts ADDED
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Axios client configuration for OMS API
3
+ */
4
+
5
+ import axios, { AxiosInstance, AxiosError } from 'axios';
6
+
7
+ export interface ApiClientConfig {
8
+ baseURL: string;
9
+ onError?: (error: AxiosError) => void;
10
+ }
11
+
12
+ let apiClient: AxiosInstance | null = null;
13
+
14
+ /**
15
+ * Initialize the API client with configuration
16
+ */
17
+ export function initializeApiClient(config: ApiClientConfig): AxiosInstance {
18
+ apiClient = axios.create({
19
+ baseURL: config.baseURL,
20
+ headers: {
21
+ 'Content-Type': 'application/json',
22
+ },
23
+ });
24
+
25
+ // Response interceptor for error handling
26
+ apiClient.interceptors.response.use(
27
+ (response) => response,
28
+ (error: AxiosError) => {
29
+ // Call custom error handler if provided
30
+ if (config.onError) {
31
+ config.onError(error);
32
+ }
33
+ return Promise.reject(error);
34
+ }
35
+ );
36
+
37
+ return apiClient;
38
+ }
39
+
40
+ /**
41
+ * Get the current API client instance
42
+ */
43
+ export function getApiClient(): AxiosInstance {
44
+ if (!apiClient) {
45
+ throw new Error(
46
+ 'API client not initialized. Make sure to wrap your app with ApiClientProvider.'
47
+ );
48
+ }
49
+ return apiClient;
50
+ }
51
+
52
+ /**
53
+ * Reset the API client (mainly for testing)
54
+ */
55
+ export function resetApiClient(): void {
56
+ apiClient = null;
57
+ }
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Cart fetcher functions
3
+ *
4
+ * These are the actual data-fetching functions used by hooks.
5
+ * They can also be imported directly in Server Components.
6
+ *
7
+ * API URL is hardcoded to https://oms-api.instock.ng
8
+ */
9
+
10
+ import { createRpcClients } from '../rpc-client';
11
+
12
+ const API_URL = 'https://oms-api.instock.ng';
13
+
14
+ /**
15
+ * Fetch a cart by ID
16
+ *
17
+ * @param cartId - Cart UUID
18
+ * @returns Cart with items, brand, and delivery zone
19
+ */
20
+ export async function fetchCart(cartId: string) {
21
+ const clients = createRpcClients(API_URL);
22
+ const res = await clients.carts[':id'].$get({
23
+ param: { id: cartId },
24
+ });
25
+ if (!res.ok) {
26
+ throw new Error(`Failed to fetch cart: ${res.statusText}`);
27
+ }
28
+ return res.json();
29
+ }
30
+
31
+ /**
32
+ * Create a new cart
33
+ *
34
+ * @param brandSlug - Brand slug
35
+ * @returns Newly created cart
36
+ */
37
+ export async function createCart(brandSlug: string) {
38
+ const clients = createRpcClients(API_URL);
39
+ const res = await clients.carts.index.$post({
40
+ json: { brandSlug },
41
+ });
42
+ if (!res.ok) {
43
+ throw new Error(`Failed to create cart: ${res.statusText}`);
44
+ }
45
+ return res.json();
46
+ }
47
+
48
+ /**
49
+ * Update a cart
50
+ *
51
+ * @param cartId - Cart UUID
52
+ * @param data - Cart update data (customer info, deliveryZoneId, etc.)
53
+ * @returns Updated cart
54
+ */
55
+ export async function updateCart(
56
+ cartId: string,
57
+ data: {
58
+ customerPhone?: string | null;
59
+ customerEmail?: string | null;
60
+ customerFirstName?: string | null;
61
+ customerLastName?: string | null;
62
+ deliveryZoneId?: string | null;
63
+ ifUnmodifiedSince?: string;
64
+ }
65
+ ) {
66
+ const clients = createRpcClients(API_URL);
67
+ const res = await clients.carts[':id'].$patch({
68
+ param: { id: cartId },
69
+ json: data,
70
+ });
71
+ if (!res.ok) {
72
+ throw new Error(`Failed to update cart: ${res.statusText}`);
73
+ }
74
+ return res.json();
75
+ }
76
+
77
+ /**
78
+ * Add an item to cart
79
+ *
80
+ * @param cartId - Cart UUID
81
+ * @param sku - Product variant SKU
82
+ * @param quantity - Quantity to add
83
+ * @returns Updated cart
84
+ */
85
+ export async function addCartItem(cartId: string, sku: string, quantity: number) {
86
+ const clients = createRpcClients(API_URL);
87
+ const res = await clients.carts[':id'].items.$post({
88
+ param: { id: cartId },
89
+ json: { sku, quantity },
90
+ });
91
+ if (!res.ok) {
92
+ throw new Error(`Failed to add item to cart: ${res.statusText}`);
93
+ }
94
+ return res.json();
95
+ }
96
+
97
+ /**
98
+ * Update a cart item quantity
99
+ *
100
+ * @param cartId - Cart UUID
101
+ * @param itemId - Cart item UUID
102
+ * @param quantity - New quantity
103
+ * @returns Updated cart
104
+ */
105
+ export async function updateCartItem(cartId: string, itemId: string, quantity: number) {
106
+ const clients = createRpcClients(API_URL);
107
+ const res = await clients.carts[':id'].items[':itemId'].$patch({
108
+ param: { id: cartId, itemId },
109
+ json: { quantity },
110
+ });
111
+ if (!res.ok) {
112
+ throw new Error(`Failed to update cart item: ${res.statusText}`);
113
+ }
114
+ return res.json();
115
+ }
116
+
117
+ /**
118
+ * Remove an item from cart
119
+ *
120
+ * @param cartId - Cart UUID
121
+ * @param itemId - Cart item UUID
122
+ * @returns Updated cart
123
+ */
124
+ export async function removeCartItem(cartId: string, itemId: string) {
125
+ const clients = createRpcClients(API_URL);
126
+ const res = await clients.carts[':id'].items[':itemId'].$delete({
127
+ param: { id: cartId, itemId },
128
+ });
129
+ if (!res.ok) {
130
+ throw new Error(`Failed to remove cart item: ${res.statusText}`);
131
+ }
132
+ return res.json();
133
+ }
134
+
135
+ /**
136
+ * Apply a discount code to cart
137
+ *
138
+ * @param cartId - Cart UUID
139
+ * @param code - Discount code
140
+ * @returns Updated cart
141
+ */
142
+ export async function applyDiscount(cartId: string, code: string) {
143
+ const clients = createRpcClients(API_URL);
144
+ const res = await clients.carts[':id']['apply-discount'].$post({
145
+ param: { id: cartId },
146
+ json: { code },
147
+ });
148
+ if (!res.ok) {
149
+ throw new Error(`Failed to apply discount: ${res.statusText}`);
150
+ }
151
+ return res.json();
152
+ }
153
+
154
+ /**
155
+ * Remove discount from cart
156
+ *
157
+ * @param cartId - Cart UUID
158
+ * @returns Updated cart
159
+ */
160
+ export async function removeDiscount(cartId: string) {
161
+ const clients = createRpcClients(API_URL);
162
+ const res = await clients.carts[':id']['remove-discount'].$post({
163
+ param: { id: cartId },
164
+ });
165
+ if (!res.ok) {
166
+ throw new Error(`Failed to remove discount: ${res.statusText}`);
167
+ }
168
+ return res.json();
169
+ }
170
+
171
+ /**
172
+ * Checkout a cart
173
+ *
174
+ * @param cartId - Cart UUID
175
+ * @param checkoutData - Checkout information (customer details, delivery, payment)
176
+ * @returns Created order
177
+ */
178
+ export async function checkoutCart(
179
+ cartId: string,
180
+ checkoutData: {
181
+ firstName: string;
182
+ lastName: string;
183
+ phone?: string;
184
+ email?: string;
185
+ address: string;
186
+ city: string;
187
+ deliveryZoneId: string;
188
+ paymentMethod: 'cod' | 'online';
189
+ paystackReference?: string;
190
+ ifUnmodifiedSince?: string;
191
+ }
192
+ ) {
193
+ const clients = createRpcClients(API_URL);
194
+ const res = await clients.carts[':id'].checkout.$post({
195
+ param: { id: cartId },
196
+ json: checkoutData,
197
+ });
198
+ if (!res.ok) {
199
+ throw new Error(`Failed to checkout cart: ${res.statusText}`);
200
+ }
201
+ return res.json();
202
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Delivery zone fetcher functions
3
+ *
4
+ * These are the actual data-fetching functions used by hooks.
5
+ * They can also be imported directly in Server Components.
6
+ *
7
+ * API URL is hardcoded to https://oms-api.instock.ng
8
+ */
9
+
10
+ import { createRpcClients } from '../rpc-client';
11
+
12
+ const API_URL = 'https://oms-api.instock.ng';
13
+
14
+ /**
15
+ * Fetch delivery zones
16
+ *
17
+ * @param brandId - Optional brand UUID to filter brand-specific zones
18
+ * @returns List of delivery zones with states
19
+ */
20
+ export async function fetchDeliveryZones(brandId?: string) {
21
+ const clients = createRpcClients(API_URL);
22
+ const res = await clients.deliveryZones.index.$get({
23
+ query: brandId ? { brandId } : {},
24
+ });
25
+ if (!res.ok) {
26
+ throw new Error(`Failed to fetch delivery zones: ${res.statusText}`);
27
+ }
28
+ return res.json();
29
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Data fetcher functions for Server Components
3
+ *
4
+ * These functions can be used directly in Next.js Server Components
5
+ * without needing React hooks. They use the same underlying logic
6
+ * as the client-side hooks, ensuring no code duplication.
7
+ *
8
+ * @example Server Component
9
+ * ```tsx
10
+ * import { fetchProductBySlug, fetchCart } from '@oms/api-client/fetchers';
11
+ *
12
+ * export default async function ProductPage({ params }) {
13
+ * const product = await fetchProductBySlug(params.slug);
14
+ * return <div>{product.name}</div>;
15
+ * }
16
+ * ```
17
+ */
18
+
19
+ export * from './products';
20
+ export * from './carts';
21
+ export * from './orders';
22
+ export * from './delivery-zones';
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Order fetcher functions
3
+ *
4
+ * These are the actual data-fetching functions used by hooks.
5
+ * They can also be imported directly in Server Components.
6
+ *
7
+ * API URL is hardcoded to https://oms-api.instock.ng
8
+ */
9
+
10
+ import { createRpcClients } from '../rpc-client';
11
+
12
+ const API_URL = 'https://oms-api.instock.ng';
13
+
14
+ /**
15
+ * Fetch an order by ID and token
16
+ *
17
+ * @param orderId - Order UUID
18
+ * @param token - User action token
19
+ * @returns Order with items, delivery zone, and brand
20
+ */
21
+ export async function fetchOrder(orderId: string, token: string) {
22
+ const clients = createRpcClients(API_URL);
23
+ const res = await clients.orders[':id'][':token'].$get({
24
+ param: { id: orderId, token },
25
+ });
26
+ if (!res.ok) {
27
+ throw new Error(`Failed to fetch order: ${res.statusText}`);
28
+ }
29
+ return res.json();
30
+ }
31
+
32
+ /**
33
+ * Confirm a prospect order
34
+ *
35
+ * @param orderId - Order UUID
36
+ * @param token - User action token
37
+ * @returns Confirmed order
38
+ */
39
+ export async function confirmOrder(orderId: string, token: string) {
40
+ const clients = createRpcClients(API_URL);
41
+ const res = await clients.orders.confirm.$post({
42
+ json: { orderId, token },
43
+ });
44
+ if (!res.ok) {
45
+ throw new Error(`Failed to confirm order: ${res.statusText}`);
46
+ }
47
+ return res.json();
48
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Product fetcher functions
3
+ *
4
+ * These are the actual data-fetching functions used by hooks.
5
+ * They can also be imported directly in Server Components.
6
+ *
7
+ * API URL is hardcoded to https://oms-api.instock.ng
8
+ */
9
+
10
+ import { createRpcClients } from '../rpc-client';
11
+
12
+ const API_URL = 'https://oms-api.instock.ng';
13
+
14
+ /**
15
+ * Fetch products by brand ID
16
+ *
17
+ * @param brandId - Brand UUID
18
+ * @returns Products for the brand with variants and availability
19
+ */
20
+ export async function fetchProductsByBrand(brandId: string) {
21
+ const clients = createRpcClients(API_URL);
22
+ const res = await clients.products[':brandId'].$get({
23
+ param: { brandId },
24
+ });
25
+ if (!res.ok) {
26
+ throw new Error(`Failed to fetch products: ${res.statusText}`);
27
+ }
28
+ return res.json();
29
+ }
30
+
31
+ /**
32
+ * Fetch a single product by slug
33
+ *
34
+ * @param slug - Product slug (e.g., 'cotton-t-shirt')
35
+ * @returns Product with variants and availability
36
+ */
37
+ export async function fetchProductBySlug(slug: string) {
38
+ const clients = createRpcClients(API_URL);
39
+ const res = await clients.products.product[':slug'].$get({
40
+ param: { slug },
41
+ });
42
+ if (!res.ok) {
43
+ throw new Error(`Failed to fetch product: ${res.statusText}`);
44
+ }
45
+ return res.json();
46
+ }