@bookinglab/booking-journey-api 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/LICENSE +21 -0
- package/README.md +178 -0
- package/dist/index.d.mts +404 -0
- package/dist/index.d.ts +404 -0
- package/dist/index.js +254 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +244 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +67 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Core API Client Types
|
|
7
|
+
*/
|
|
8
|
+
interface ApiClientConfig {
|
|
9
|
+
baseUrl: string;
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
timeout?: number;
|
|
12
|
+
}
|
|
13
|
+
interface RequestOptions extends RequestInit {
|
|
14
|
+
params?: Record<string, string | number | boolean | undefined>;
|
|
15
|
+
}
|
|
16
|
+
interface ApiResponse<T = any> {
|
|
17
|
+
data: T;
|
|
18
|
+
status: number;
|
|
19
|
+
headers: Headers;
|
|
20
|
+
}
|
|
21
|
+
interface ApiError {
|
|
22
|
+
message: string;
|
|
23
|
+
status?: number;
|
|
24
|
+
code?: string;
|
|
25
|
+
details?: any;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* JRNI Configuration
|
|
29
|
+
*/
|
|
30
|
+
interface JrniConfig {
|
|
31
|
+
appId: string;
|
|
32
|
+
appKey: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* JRNI API Types
|
|
36
|
+
*/
|
|
37
|
+
interface LoginRequest {
|
|
38
|
+
email: string;
|
|
39
|
+
password: string;
|
|
40
|
+
}
|
|
41
|
+
interface LoginResponse {
|
|
42
|
+
email: string;
|
|
43
|
+
auth_token: string;
|
|
44
|
+
company_id: number;
|
|
45
|
+
path: string;
|
|
46
|
+
role?: string;
|
|
47
|
+
_embedded?: {
|
|
48
|
+
members?: Array<{
|
|
49
|
+
id: number;
|
|
50
|
+
name: string;
|
|
51
|
+
first_name: string;
|
|
52
|
+
last_name: string;
|
|
53
|
+
email: string;
|
|
54
|
+
[key: string]: any;
|
|
55
|
+
}>;
|
|
56
|
+
administrators?: any[];
|
|
57
|
+
};
|
|
58
|
+
_links?: any;
|
|
59
|
+
}
|
|
60
|
+
interface JrniService {
|
|
61
|
+
id: number;
|
|
62
|
+
name: string;
|
|
63
|
+
description: string;
|
|
64
|
+
durations: number[];
|
|
65
|
+
prices: number[];
|
|
66
|
+
detail_group_id: number;
|
|
67
|
+
listed_durations: any[];
|
|
68
|
+
extra: Record<string, any>;
|
|
69
|
+
booking_time_step: number;
|
|
70
|
+
can_refund_automatically: boolean;
|
|
71
|
+
is_event_group: boolean;
|
|
72
|
+
type: string;
|
|
73
|
+
group_id: number | null;
|
|
74
|
+
deleted: boolean;
|
|
75
|
+
queuing_disabled: boolean;
|
|
76
|
+
company_id: number;
|
|
77
|
+
min_advance_period: number;
|
|
78
|
+
max_advance_period: number;
|
|
79
|
+
min_cancel_period: number;
|
|
80
|
+
booking_type_public: string;
|
|
81
|
+
booking_type: number;
|
|
82
|
+
mbooking_type: number;
|
|
83
|
+
min_bookings: number;
|
|
84
|
+
max_bookings: number;
|
|
85
|
+
method_of_appointment: string;
|
|
86
|
+
groups: any[];
|
|
87
|
+
order: number;
|
|
88
|
+
child_level_service: boolean;
|
|
89
|
+
global_id: number;
|
|
90
|
+
availability: number;
|
|
91
|
+
prices_in_major_units: number[];
|
|
92
|
+
combine_resource_and_staff: boolean;
|
|
93
|
+
disabled: boolean;
|
|
94
|
+
_links: Record<string, any>;
|
|
95
|
+
}
|
|
96
|
+
interface ServicesResponse {
|
|
97
|
+
total_entries: number;
|
|
98
|
+
_embedded: {
|
|
99
|
+
services: JrniService[];
|
|
100
|
+
};
|
|
101
|
+
_links: Record<string, any>;
|
|
102
|
+
}
|
|
103
|
+
interface QuestionOption {
|
|
104
|
+
name: string;
|
|
105
|
+
price: number;
|
|
106
|
+
is_default: boolean;
|
|
107
|
+
id: number;
|
|
108
|
+
}
|
|
109
|
+
interface Question {
|
|
110
|
+
id: number;
|
|
111
|
+
name: string;
|
|
112
|
+
required: boolean;
|
|
113
|
+
important: boolean;
|
|
114
|
+
admin_only: boolean;
|
|
115
|
+
applies_to: number;
|
|
116
|
+
ask_member: boolean;
|
|
117
|
+
detail_type: string;
|
|
118
|
+
options?: QuestionOption[];
|
|
119
|
+
settings: Record<string, any>;
|
|
120
|
+
price: number;
|
|
121
|
+
price_per_booking: boolean;
|
|
122
|
+
outcome: boolean;
|
|
123
|
+
hide_on_customer_journey: boolean;
|
|
124
|
+
default?: string;
|
|
125
|
+
help_text?: string;
|
|
126
|
+
_links: Record<string, any>;
|
|
127
|
+
}
|
|
128
|
+
interface QuestionsResponse {
|
|
129
|
+
company_id: number;
|
|
130
|
+
questions: Question[];
|
|
131
|
+
name: string;
|
|
132
|
+
_links: Record<string, any>;
|
|
133
|
+
}
|
|
134
|
+
interface Location {
|
|
135
|
+
id: number;
|
|
136
|
+
name: string;
|
|
137
|
+
description: string;
|
|
138
|
+
company_type: string;
|
|
139
|
+
address: {
|
|
140
|
+
id: number;
|
|
141
|
+
address1: string;
|
|
142
|
+
address2: string;
|
|
143
|
+
address3: string;
|
|
144
|
+
address4: string;
|
|
145
|
+
address5: string;
|
|
146
|
+
postcode: string;
|
|
147
|
+
country: string;
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
interface LocationsResponse {
|
|
151
|
+
total_entries: number;
|
|
152
|
+
_embedded: {
|
|
153
|
+
resources: Location[];
|
|
154
|
+
};
|
|
155
|
+
_links: Record<string, any>;
|
|
156
|
+
}
|
|
157
|
+
interface Vehicle {
|
|
158
|
+
id: number;
|
|
159
|
+
name: string;
|
|
160
|
+
description: string;
|
|
161
|
+
type: string;
|
|
162
|
+
extra: {
|
|
163
|
+
vehicle_registration?: string;
|
|
164
|
+
vehicle_make?: string;
|
|
165
|
+
vehicle_model?: string;
|
|
166
|
+
vehicle_size?: string;
|
|
167
|
+
fuel_type?: string;
|
|
168
|
+
vehicle_brand?: string;
|
|
169
|
+
vehicle_transmission?: string;
|
|
170
|
+
base_location?: string;
|
|
171
|
+
driver_name?: string;
|
|
172
|
+
driver_email?: string;
|
|
173
|
+
};
|
|
174
|
+
group_id: number | null;
|
|
175
|
+
deleted: boolean;
|
|
176
|
+
disabled: boolean;
|
|
177
|
+
company_id: number;
|
|
178
|
+
order: number;
|
|
179
|
+
created_at: string;
|
|
180
|
+
updated_at: string;
|
|
181
|
+
_links: Record<string, any>;
|
|
182
|
+
}
|
|
183
|
+
interface VehiclesResponse {
|
|
184
|
+
total_entries: number;
|
|
185
|
+
_embedded: {
|
|
186
|
+
people: Vehicle[];
|
|
187
|
+
};
|
|
188
|
+
_links: Record<string, any>;
|
|
189
|
+
}
|
|
190
|
+
interface AddBasketItemRequest {
|
|
191
|
+
service_id: number;
|
|
192
|
+
start: string;
|
|
193
|
+
duration: number;
|
|
194
|
+
company_id: string;
|
|
195
|
+
person_id: number;
|
|
196
|
+
questions: Array<{
|
|
197
|
+
id: string;
|
|
198
|
+
answer: string;
|
|
199
|
+
}>;
|
|
200
|
+
}
|
|
201
|
+
interface AvailabilityTime {
|
|
202
|
+
start: string;
|
|
203
|
+
available: boolean;
|
|
204
|
+
durations: number[];
|
|
205
|
+
prices: number[];
|
|
206
|
+
_links: Record<string, any>;
|
|
207
|
+
}
|
|
208
|
+
interface AvailabilityTimesResponse {
|
|
209
|
+
times: AvailabilityTime[];
|
|
210
|
+
}
|
|
211
|
+
interface JrniBooking {
|
|
212
|
+
id: number;
|
|
213
|
+
full_describe: string;
|
|
214
|
+
describe: string;
|
|
215
|
+
person_name: string;
|
|
216
|
+
datetime: string;
|
|
217
|
+
end_datetime: string;
|
|
218
|
+
duration: number;
|
|
219
|
+
service_name: string;
|
|
220
|
+
service_id: number;
|
|
221
|
+
status: string;
|
|
222
|
+
is_cancelled: boolean;
|
|
223
|
+
min_cancellation_time?: number;
|
|
224
|
+
person_ids?: number[];
|
|
225
|
+
settings?: {
|
|
226
|
+
current_multi_stat?: string;
|
|
227
|
+
multi_stats?: Record<string, string>;
|
|
228
|
+
obfuscated_id?: string;
|
|
229
|
+
who_updated?: string;
|
|
230
|
+
};
|
|
231
|
+
questions: Record<string, {
|
|
232
|
+
answer: string;
|
|
233
|
+
answer_id: number | null;
|
|
234
|
+
name: string;
|
|
235
|
+
}>;
|
|
236
|
+
_embedded?: {
|
|
237
|
+
answers?: Array<{
|
|
238
|
+
id: number;
|
|
239
|
+
value: string;
|
|
240
|
+
question_text: string;
|
|
241
|
+
}>;
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
interface MemberBookingsResponse {
|
|
245
|
+
total_entries: number;
|
|
246
|
+
_embedded: {
|
|
247
|
+
bookings: JrniBooking[];
|
|
248
|
+
};
|
|
249
|
+
_links: Record<string, any>;
|
|
250
|
+
}
|
|
251
|
+
interface PersonImage {
|
|
252
|
+
id: number;
|
|
253
|
+
name: string;
|
|
254
|
+
url: string;
|
|
255
|
+
order: number;
|
|
256
|
+
person_id?: number;
|
|
257
|
+
}
|
|
258
|
+
interface PersonImagesResponse {
|
|
259
|
+
_embedded: {
|
|
260
|
+
images: PersonImage[];
|
|
261
|
+
};
|
|
262
|
+
_links: Record<string, any>;
|
|
263
|
+
}
|
|
264
|
+
interface UpdateClientDetailsData {
|
|
265
|
+
first_name: string;
|
|
266
|
+
last_name: string;
|
|
267
|
+
mobile: string;
|
|
268
|
+
questions?: Record<string, {
|
|
269
|
+
answer: string;
|
|
270
|
+
}>;
|
|
271
|
+
}
|
|
272
|
+
interface UpdateMemberDetailsData {
|
|
273
|
+
first_name: string;
|
|
274
|
+
last_name: string;
|
|
275
|
+
mobile?: string;
|
|
276
|
+
q?: Record<string, {
|
|
277
|
+
answer: string;
|
|
278
|
+
answer_id: number;
|
|
279
|
+
name: string;
|
|
280
|
+
}>;
|
|
281
|
+
answers?: Array<{
|
|
282
|
+
question_id: number;
|
|
283
|
+
name: string;
|
|
284
|
+
answer: string;
|
|
285
|
+
answer_id: number;
|
|
286
|
+
}>;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Core API Client
|
|
291
|
+
* Base class for making HTTP requests
|
|
292
|
+
*/
|
|
293
|
+
|
|
294
|
+
declare class ApiClient {
|
|
295
|
+
protected baseUrl: string;
|
|
296
|
+
protected headers: Record<string, string>;
|
|
297
|
+
protected timeout: number;
|
|
298
|
+
constructor(config: ApiClientConfig);
|
|
299
|
+
/**
|
|
300
|
+
* Update client configuration
|
|
301
|
+
*/
|
|
302
|
+
setConfig(config: Partial<ApiClientConfig>): void;
|
|
303
|
+
/**
|
|
304
|
+
* Set authorization token
|
|
305
|
+
*/
|
|
306
|
+
setAuthToken(token: string): void;
|
|
307
|
+
/**
|
|
308
|
+
* Make an HTTP request
|
|
309
|
+
*/
|
|
310
|
+
protected request<T>(endpoint: string, options?: RequestOptions): Promise<ApiResponse<T>>;
|
|
311
|
+
/**
|
|
312
|
+
* GET request
|
|
313
|
+
*/
|
|
314
|
+
protected get<T>(endpoint: string, options?: RequestOptions): Promise<ApiResponse<T>>;
|
|
315
|
+
/**
|
|
316
|
+
* POST request
|
|
317
|
+
*/
|
|
318
|
+
protected post<T>(endpoint: string, body?: any, options?: RequestOptions): Promise<ApiResponse<T>>;
|
|
319
|
+
/**
|
|
320
|
+
* PUT request
|
|
321
|
+
*/
|
|
322
|
+
protected put<T>(endpoint: string, body?: any, options?: RequestOptions): Promise<ApiResponse<T>>;
|
|
323
|
+
/**
|
|
324
|
+
* DELETE request
|
|
325
|
+
*/
|
|
326
|
+
protected delete<T>(endpoint: string, options?: RequestOptions): Promise<ApiResponse<T>>;
|
|
327
|
+
/**
|
|
328
|
+
* Handle error responses
|
|
329
|
+
*/
|
|
330
|
+
private handleError;
|
|
331
|
+
/**
|
|
332
|
+
* Normalize errors to consistent format
|
|
333
|
+
*/
|
|
334
|
+
private normalizeError;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* JRNI API Client
|
|
339
|
+
* Provides methods for interacting with the JRNI API
|
|
340
|
+
*/
|
|
341
|
+
|
|
342
|
+
declare class JrniClient extends ApiClient {
|
|
343
|
+
private appId;
|
|
344
|
+
private appKey;
|
|
345
|
+
constructor(baseUrl: string, config: JrniConfig);
|
|
346
|
+
/**
|
|
347
|
+
* Login to JRNI
|
|
348
|
+
*/
|
|
349
|
+
login(credentials: LoginRequest): Promise<ApiResponse<LoginResponse>>;
|
|
350
|
+
/**
|
|
351
|
+
* Update JRNI configuration
|
|
352
|
+
*/
|
|
353
|
+
setJrniConfig(config: Partial<JrniConfig>): void;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Create a new JRNI client instance
|
|
357
|
+
*/
|
|
358
|
+
declare function createJrniClient(baseUrl: string, config: JrniConfig): JrniClient;
|
|
359
|
+
|
|
360
|
+
interface ApiClientContextValue {
|
|
361
|
+
jrniClient: JrniClient | null;
|
|
362
|
+
}
|
|
363
|
+
interface ApiClientProviderProps {
|
|
364
|
+
children: ReactNode;
|
|
365
|
+
jrniBaseUrl?: string;
|
|
366
|
+
jrniConfig?: JrniConfig;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Combined provider for multiple API clients
|
|
370
|
+
*/
|
|
371
|
+
declare function ApiClientProvider({ children, jrniBaseUrl, jrniConfig, }: ApiClientProviderProps): react_jsx_runtime.JSX.Element;
|
|
372
|
+
/**
|
|
373
|
+
* Hook to access API client context
|
|
374
|
+
*/
|
|
375
|
+
declare function useApiClientContext(): ApiClientContextValue;
|
|
376
|
+
|
|
377
|
+
interface JrniProviderProps {
|
|
378
|
+
children: ReactNode;
|
|
379
|
+
baseUrl: string;
|
|
380
|
+
config: JrniConfig;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Provider component for JRNI client
|
|
384
|
+
*/
|
|
385
|
+
declare function JrniProvider({ children, baseUrl, config }: JrniProviderProps): react_jsx_runtime.JSX.Element;
|
|
386
|
+
/**
|
|
387
|
+
* Hook to access JRNI client from context
|
|
388
|
+
*/
|
|
389
|
+
declare function useJrniContext(): JrniClient;
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Hook to access API clients from context
|
|
393
|
+
*/
|
|
394
|
+
/**
|
|
395
|
+
* Hook to get JRNI client from either ApiClientProvider or JrniProvider
|
|
396
|
+
*/
|
|
397
|
+
declare function useJrniClient(): JrniClient;
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Hook for JRNI login
|
|
401
|
+
*/
|
|
402
|
+
declare function useLogin(): _tanstack_react_query.UseMutationResult<LoginResponse, Error, LoginRequest, unknown>;
|
|
403
|
+
|
|
404
|
+
export { type AddBasketItemRequest, ApiClient, type ApiClientConfig, ApiClientProvider, type ApiError, type ApiResponse, type AvailabilityTime, type AvailabilityTimesResponse, type JrniBooking, JrniClient, type JrniConfig, JrniProvider, type JrniService, type Location, type LocationsResponse, type LoginRequest, type LoginResponse, type MemberBookingsResponse, type PersonImage, type PersonImagesResponse, type Question, type QuestionOption, type QuestionsResponse, type RequestOptions, type ServicesResponse, type UpdateClientDetailsData, type UpdateMemberDetailsData, type Vehicle, type VehiclesResponse, createJrniClient, useApiClientContext, useJrniClient, useJrniContext, useLogin };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
var reactQuery = require('@tanstack/react-query');
|
|
6
|
+
|
|
7
|
+
// src/core.ts
|
|
8
|
+
var ApiClient = class {
|
|
9
|
+
constructor(config) {
|
|
10
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
11
|
+
this.headers = config.headers || {};
|
|
12
|
+
this.timeout = config.timeout || 3e4;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Update client configuration
|
|
16
|
+
*/
|
|
17
|
+
setConfig(config) {
|
|
18
|
+
if (config.baseUrl) this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
19
|
+
if (config.headers) this.headers = { ...this.headers, ...config.headers };
|
|
20
|
+
if (config.timeout) this.timeout = config.timeout;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Set authorization token
|
|
24
|
+
*/
|
|
25
|
+
setAuthToken(token) {
|
|
26
|
+
this.headers["Authorization"] = `Bearer ${token}`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Make an HTTP request
|
|
30
|
+
*/
|
|
31
|
+
async request(endpoint, options = {}) {
|
|
32
|
+
const { params, ...fetchOptions } = options;
|
|
33
|
+
let url = `${this.baseUrl}${endpoint}`;
|
|
34
|
+
if (params) {
|
|
35
|
+
const searchParams = new URLSearchParams();
|
|
36
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
37
|
+
if (value !== void 0) {
|
|
38
|
+
searchParams.append(key, String(value));
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
const queryString = searchParams.toString();
|
|
42
|
+
if (queryString) {
|
|
43
|
+
url += `?${queryString}`;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const headers = {
|
|
47
|
+
"Content-Type": "application/json",
|
|
48
|
+
...this.headers,
|
|
49
|
+
...fetchOptions.headers || {}
|
|
50
|
+
};
|
|
51
|
+
const controller = new AbortController();
|
|
52
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
53
|
+
try {
|
|
54
|
+
const response = await fetch(url, {
|
|
55
|
+
...fetchOptions,
|
|
56
|
+
headers,
|
|
57
|
+
signal: controller.signal
|
|
58
|
+
});
|
|
59
|
+
clearTimeout(timeoutId);
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
throw await this.handleError(response);
|
|
62
|
+
}
|
|
63
|
+
const data = await response.json();
|
|
64
|
+
return {
|
|
65
|
+
data,
|
|
66
|
+
status: response.status,
|
|
67
|
+
headers: response.headers
|
|
68
|
+
};
|
|
69
|
+
} catch (error) {
|
|
70
|
+
clearTimeout(timeoutId);
|
|
71
|
+
throw this.normalizeError(error);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* GET request
|
|
76
|
+
*/
|
|
77
|
+
async get(endpoint, options) {
|
|
78
|
+
return this.request(endpoint, { ...options, method: "GET" });
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* POST request
|
|
82
|
+
*/
|
|
83
|
+
async post(endpoint, body, options) {
|
|
84
|
+
return this.request(endpoint, {
|
|
85
|
+
...options,
|
|
86
|
+
method: "POST",
|
|
87
|
+
body: body ? JSON.stringify(body) : void 0
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* PUT request
|
|
92
|
+
*/
|
|
93
|
+
async put(endpoint, body, options) {
|
|
94
|
+
return this.request(endpoint, {
|
|
95
|
+
...options,
|
|
96
|
+
method: "PUT",
|
|
97
|
+
body: body ? JSON.stringify(body) : void 0
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* DELETE request
|
|
102
|
+
*/
|
|
103
|
+
async delete(endpoint, options) {
|
|
104
|
+
return this.request(endpoint, { ...options, method: "DELETE" });
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Handle error responses
|
|
108
|
+
*/
|
|
109
|
+
async handleError(response) {
|
|
110
|
+
let message = `HTTP ${response.status}: ${response.statusText}`;
|
|
111
|
+
let details;
|
|
112
|
+
try {
|
|
113
|
+
details = await response.json();
|
|
114
|
+
if (details.message) {
|
|
115
|
+
message = details.message;
|
|
116
|
+
} else if (details.error) {
|
|
117
|
+
message = details.error;
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
message,
|
|
123
|
+
status: response.status,
|
|
124
|
+
details
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Normalize errors to consistent format
|
|
129
|
+
*/
|
|
130
|
+
normalizeError(error) {
|
|
131
|
+
if (error.name === "AbortError") {
|
|
132
|
+
return {
|
|
133
|
+
message: "Request timeout",
|
|
134
|
+
code: "TIMEOUT"
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
if ("status" in error) {
|
|
138
|
+
return error;
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
message: error.message || "Unknown error",
|
|
142
|
+
code: "UNKNOWN"
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// src/jrni.ts
|
|
148
|
+
var JrniClient = class extends ApiClient {
|
|
149
|
+
constructor(baseUrl, config) {
|
|
150
|
+
super({ baseUrl });
|
|
151
|
+
this.appId = config.appId;
|
|
152
|
+
this.appKey = config.appKey;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Login to JRNI
|
|
156
|
+
*/
|
|
157
|
+
async login(credentials) {
|
|
158
|
+
return this.post("/login", credentials, {
|
|
159
|
+
headers: {
|
|
160
|
+
"Content-Type": "application/json",
|
|
161
|
+
"App-Id": this.appId,
|
|
162
|
+
"App-Key": this.appKey
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Update JRNI configuration
|
|
168
|
+
*/
|
|
169
|
+
setJrniConfig(config) {
|
|
170
|
+
if (config.appId) this.appId = config.appId;
|
|
171
|
+
if (config.appKey) this.appKey = config.appKey;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
function createJrniClient(baseUrl, config) {
|
|
175
|
+
return new JrniClient(baseUrl, config);
|
|
176
|
+
}
|
|
177
|
+
var ApiClientContext = react.createContext(void 0);
|
|
178
|
+
function ApiClientProvider({
|
|
179
|
+
children,
|
|
180
|
+
jrniBaseUrl,
|
|
181
|
+
jrniConfig
|
|
182
|
+
}) {
|
|
183
|
+
const jrniClient = react.useMemo(() => {
|
|
184
|
+
if (jrniBaseUrl && jrniConfig) {
|
|
185
|
+
return new JrniClient(jrniBaseUrl, jrniConfig);
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}, [jrniBaseUrl, jrniConfig?.appId, jrniConfig?.appKey]);
|
|
189
|
+
const value = react.useMemo(
|
|
190
|
+
() => ({
|
|
191
|
+
jrniClient
|
|
192
|
+
}),
|
|
193
|
+
[jrniClient]
|
|
194
|
+
);
|
|
195
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ApiClientContext.Provider, { value, children });
|
|
196
|
+
}
|
|
197
|
+
function useApiClientContext() {
|
|
198
|
+
const context = react.useContext(ApiClientContext);
|
|
199
|
+
if (context === void 0) {
|
|
200
|
+
throw new Error("useApiClientContext must be used within an ApiClientProvider");
|
|
201
|
+
}
|
|
202
|
+
return context;
|
|
203
|
+
}
|
|
204
|
+
var JrniContext = react.createContext(void 0);
|
|
205
|
+
function JrniProvider({ children, baseUrl, config }) {
|
|
206
|
+
const client = react.useMemo(() => {
|
|
207
|
+
return new JrniClient(baseUrl, config);
|
|
208
|
+
}, [baseUrl, config.appId, config.appKey]);
|
|
209
|
+
const value = react.useMemo(() => ({ client }), [client]);
|
|
210
|
+
return /* @__PURE__ */ jsxRuntime.jsx(JrniContext.Provider, { value, children });
|
|
211
|
+
}
|
|
212
|
+
function useJrniContext() {
|
|
213
|
+
const context = react.useContext(JrniContext);
|
|
214
|
+
if (context === void 0) {
|
|
215
|
+
throw new Error("useJrniContext must be used within a JrniProvider");
|
|
216
|
+
}
|
|
217
|
+
return context.client;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// src/hooks/useApiClient.ts
|
|
221
|
+
function useJrniClient() {
|
|
222
|
+
try {
|
|
223
|
+
return useJrniContext();
|
|
224
|
+
} catch {
|
|
225
|
+
const context = useApiClientContext();
|
|
226
|
+
if (!context.jrniClient) {
|
|
227
|
+
throw new Error(
|
|
228
|
+
"JRNI client not configured. Wrap your app with ApiClientProvider or JrniProvider."
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
return context.jrniClient;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function useLogin() {
|
|
235
|
+
const client = useJrniClient();
|
|
236
|
+
return reactQuery.useMutation({
|
|
237
|
+
mutationFn: async (credentials) => {
|
|
238
|
+
const response = await client.login(credentials);
|
|
239
|
+
return response.data;
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
exports.ApiClient = ApiClient;
|
|
245
|
+
exports.ApiClientProvider = ApiClientProvider;
|
|
246
|
+
exports.JrniClient = JrniClient;
|
|
247
|
+
exports.JrniProvider = JrniProvider;
|
|
248
|
+
exports.createJrniClient = createJrniClient;
|
|
249
|
+
exports.useApiClientContext = useApiClientContext;
|
|
250
|
+
exports.useJrniClient = useJrniClient;
|
|
251
|
+
exports.useJrniContext = useJrniContext;
|
|
252
|
+
exports.useLogin = useLogin;
|
|
253
|
+
//# sourceMappingURL=index.js.map
|
|
254
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core.ts","../src/jrni.ts","../src/providers/ApiClientProvider.tsx","../src/providers/JrniProvider.tsx","../src/hooks/useApiClient.ts","../src/hooks/useJrni.ts"],"names":["createContext","useMemo","jsx","useContext","useMutation"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Core API Client\n * Base class for making HTTP requests\n */\n\nimport { ApiClientConfig, RequestOptions, ApiResponse, ApiError } from './types';\n\nexport class ApiClient {\n protected baseUrl: string;\n protected headers: Record<string, string>;\n protected timeout: number;\n\n constructor(config: ApiClientConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/$/, '');\n this.headers = config.headers || {};\n this.timeout = config.timeout || 30000;\n }\n\n /**\n * Update client configuration\n */\n setConfig(config: Partial<ApiClientConfig>) {\n if (config.baseUrl) this.baseUrl = config.baseUrl.replace(/\\/$/, '');\n if (config.headers) this.headers = { ...this.headers, ...config.headers };\n if (config.timeout) this.timeout = config.timeout;\n }\n\n /**\n * Set authorization token\n */\n setAuthToken(token: string) {\n this.headers['Authorization'] = `Bearer ${token}`;\n }\n\n /**\n * Make an HTTP request\n */\n protected async request<T>(\n endpoint: string,\n options: RequestOptions = {}\n ): Promise<ApiResponse<T>> {\n const { params, ...fetchOptions } = options;\n\n // Build URL with query parameters\n let url = `${this.baseUrl}${endpoint}`;\n if (params) {\n const searchParams = new URLSearchParams();\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined) {\n searchParams.append(key, String(value));\n }\n });\n const queryString = searchParams.toString();\n if (queryString) {\n url += `?${queryString}`;\n }\n }\n\n // Merge headers\n const headers = {\n 'Content-Type': 'application/json',\n ...this.headers,\n ...((fetchOptions.headers as Record<string, string>) || {}),\n };\n\n // Create abort controller for timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(url, {\n ...fetchOptions,\n headers,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n throw await this.handleError(response);\n }\n\n const data = await response.json();\n\n return {\n data,\n status: response.status,\n headers: response.headers,\n };\n } catch (error) {\n clearTimeout(timeoutId);\n throw this.normalizeError(error as Error);\n }\n }\n\n /**\n * GET request\n */\n protected async get<T>(endpoint: string, options?: RequestOptions): Promise<ApiResponse<T>> {\n return this.request<T>(endpoint, { ...options, method: 'GET' });\n }\n\n /**\n * POST request\n */\n protected async post<T>(\n endpoint: string,\n body?: any,\n options?: RequestOptions\n ): Promise<ApiResponse<T>> {\n return this.request<T>(endpoint, {\n ...options,\n method: 'POST',\n body: body ? JSON.stringify(body) : undefined,\n });\n }\n\n /**\n * PUT request\n */\n protected async put<T>(\n endpoint: string,\n body?: any,\n options?: RequestOptions\n ): Promise<ApiResponse<T>> {\n return this.request<T>(endpoint, {\n ...options,\n method: 'PUT',\n body: body ? JSON.stringify(body) : undefined,\n });\n }\n\n /**\n * DELETE request\n */\n protected async delete<T>(endpoint: string, options?: RequestOptions): Promise<ApiResponse<T>> {\n return this.request<T>(endpoint, { ...options, method: 'DELETE' });\n }\n\n /**\n * Handle error responses\n */\n private async handleError(response: Response): Promise<ApiError> {\n let message = `HTTP ${response.status}: ${response.statusText}`;\n let details: any;\n\n try {\n details = await response.json();\n if (details.message) {\n message = details.message;\n } else if (details.error) {\n message = details.error;\n }\n } catch {\n // Response body is not JSON\n }\n\n return {\n message,\n status: response.status,\n details,\n };\n }\n\n /**\n * Normalize errors to consistent format\n */\n private normalizeError(error: Error): ApiError {\n if (error.name === 'AbortError') {\n return {\n message: 'Request timeout',\n code: 'TIMEOUT',\n };\n }\n\n if ('status' in error) {\n return error as ApiError;\n }\n\n return {\n message: error.message || 'Unknown error',\n code: 'UNKNOWN',\n };\n }\n}\n","/**\n * JRNI API Client\n * Provides methods for interacting with the JRNI API\n */\n\nimport { ApiClient } from './core';\nimport { LoginRequest, LoginResponse, ApiResponse, JrniConfig } from './types';\n\nexport class JrniClient extends ApiClient {\n private appId: string;\n private appKey: string;\n\n constructor(baseUrl: string, config: JrniConfig) {\n super({ baseUrl });\n this.appId = config.appId;\n this.appKey = config.appKey;\n }\n\n /**\n * Login to JRNI\n */\n async login(credentials: LoginRequest): Promise<ApiResponse<LoginResponse>> {\n return this.post<LoginResponse>('/login', credentials, {\n headers: {\n 'Content-Type': 'application/json',\n 'App-Id': this.appId,\n 'App-Key': this.appKey,\n },\n });\n }\n\n /**\n * Update JRNI configuration\n */\n setJrniConfig(config: Partial<JrniConfig>) {\n if (config.appId) this.appId = config.appId;\n if (config.appKey) this.appKey = config.appKey;\n }\n}\n\n/**\n * Create a new JRNI client instance\n */\nexport function createJrniClient(baseUrl: string, config: JrniConfig) {\n return new JrniClient(baseUrl, config);\n}\n","/**\n * React Context Provider for API Clients\n * Combined provider for applications using multiple API clients\n */\n\nimport React, { createContext, useContext, useMemo, ReactNode } from 'react';\nimport { JrniClient } from '../jrni';\nimport { JrniConfig } from '../types';\n\ninterface ApiClientContextValue {\n jrniClient: JrniClient | null;\n}\n\ninterface ApiClientProviderProps {\n children: ReactNode;\n jrniBaseUrl?: string;\n jrniConfig?: JrniConfig;\n}\n\nconst ApiClientContext = createContext<ApiClientContextValue | undefined>(undefined);\n\n/**\n * Combined provider for multiple API clients\n */\nexport function ApiClientProvider({\n children,\n jrniBaseUrl,\n jrniConfig,\n}: ApiClientProviderProps) {\n const jrniClient = useMemo(() => {\n if (jrniBaseUrl && jrniConfig) {\n return new JrniClient(jrniBaseUrl, jrniConfig);\n }\n return null;\n }, [jrniBaseUrl, jrniConfig?.appId, jrniConfig?.appKey]);\n\n const value = useMemo(\n () => ({\n jrniClient,\n }),\n [jrniClient]\n );\n\n return <ApiClientContext.Provider value={value}>{children}</ApiClientContext.Provider>;\n}\n\n/**\n * Hook to access API client context\n */\nexport function useApiClientContext() {\n const context = useContext(ApiClientContext);\n if (context === undefined) {\n throw new Error('useApiClientContext must be used within an ApiClientProvider');\n }\n return context;\n}\n","/**\n * React Context Provider for JRNI API Client\n * Standalone provider for apps that only use JRNI\n */\n\nimport React, { createContext, useContext, useMemo, ReactNode } from 'react';\nimport { JrniClient } from '../jrni';\nimport { JrniConfig } from '../types';\n\ninterface JrniContextValue {\n client: JrniClient;\n}\n\ninterface JrniProviderProps {\n children: ReactNode;\n baseUrl: string;\n config: JrniConfig;\n}\n\nconst JrniContext = createContext<JrniContextValue | undefined>(undefined);\n\n/**\n * Provider component for JRNI client\n */\nexport function JrniProvider({ children, baseUrl, config }: JrniProviderProps) {\n const client = useMemo(() => {\n return new JrniClient(baseUrl, config);\n }, [baseUrl, config.appId, config.appKey]);\n\n const value = useMemo(() => ({ client }), [client]);\n\n return <JrniContext.Provider value={value}>{children}</JrniContext.Provider>;\n}\n\n/**\n * Hook to access JRNI client from context\n */\nexport function useJrniContext() {\n const context = useContext(JrniContext);\n if (context === undefined) {\n throw new Error('useJrniContext must be used within a JrniProvider');\n }\n return context.client;\n}\n","/**\n * Hook to access API clients from context\n */\n\nimport { useApiClientContext } from '../providers/ApiClientProvider';\nimport { useJrniContext } from '../providers/JrniProvider';\n\n/**\n * Hook to get JRNI client from either ApiClientProvider or JrniProvider\n */\nexport function useJrniClient() {\n // Try to get from standalone provider first\n try {\n return useJrniContext();\n } catch {\n // Fall back to combined provider\n const context = useApiClientContext();\n if (!context.jrniClient) {\n throw new Error(\n 'JRNI client not configured. Wrap your app with ApiClientProvider or JrniProvider.'\n );\n }\n return context.jrniClient;\n }\n}\n","/**\n * React hooks for JRNI API\n */\n\nimport { useMutation } from '@tanstack/react-query';\nimport { useJrniClient } from './useApiClient';\nimport { LoginRequest, LoginResponse } from '../types';\n\n/**\n * Hook for JRNI login\n */\nexport function useLogin() {\n const client = useJrniClient();\n\n return useMutation({\n mutationFn: async (credentials: LoginRequest) => {\n const response = await client.login(credentials);\n return response.data;\n },\n });\n}\n"]}
|