@routeflow/sdk 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 +304 -0
- package/dist/api/client.d.ts +54 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +119 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/mutations/auth.d.ts +57 -0
- package/dist/api/mutations/auth.d.ts.map +1 -0
- package/dist/api/mutations/auth.js +109 -0
- package/dist/api/mutations/auth.js.map +1 -0
- package/dist/api/mutations/index.d.ts +8 -0
- package/dist/api/mutations/index.d.ts.map +1 -0
- package/dist/api/mutations/index.js +29 -0
- package/dist/api/mutations/index.js.map +1 -0
- package/dist/api/mutations/runs.d.ts +41 -0
- package/dist/api/mutations/runs.d.ts.map +1 -0
- package/dist/api/mutations/runs.js +132 -0
- package/dist/api/mutations/runs.js.map +1 -0
- package/dist/api/mutations/stops.d.ts +44 -0
- package/dist/api/mutations/stops.d.ts.map +1 -0
- package/dist/api/mutations/stops.js +87 -0
- package/dist/api/mutations/stops.js.map +1 -0
- package/dist/api/mutations/users.d.ts +51 -0
- package/dist/api/mutations/users.d.ts.map +1 -0
- package/dist/api/mutations/users.js +69 -0
- package/dist/api/mutations/users.js.map +1 -0
- package/dist/api/queries/auth.d.ts +13 -0
- package/dist/api/queries/auth.d.ts.map +1 -0
- package/dist/api/queries/auth.js +29 -0
- package/dist/api/queries/auth.js.map +1 -0
- package/dist/api/queries/drivers.d.ts +22 -0
- package/dist/api/queries/drivers.d.ts.map +1 -0
- package/dist/api/queries/drivers.js +56 -0
- package/dist/api/queries/drivers.js.map +1 -0
- package/dist/api/queries/index.d.ts +8 -0
- package/dist/api/queries/index.d.ts.map +1 -0
- package/dist/api/queries/index.js +19 -0
- package/dist/api/queries/index.js.map +1 -0
- package/dist/api/queries/runs.d.ts +33 -0
- package/dist/api/queries/runs.d.ts.map +1 -0
- package/dist/api/queries/runs.js +90 -0
- package/dist/api/queries/runs.js.map +1 -0
- package/dist/api/queries/tracking.d.ts +22 -0
- package/dist/api/queries/tracking.d.ts.map +1 -0
- package/dist/api/queries/tracking.js +54 -0
- package/dist/api/queries/tracking.js.map +1 -0
- package/dist/api/types.d.ts +87 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +74 -0
- package/dist/api/types.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +58 -0
- package/dist/index.js.map +1 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# @routeflow/sdk
|
|
2
|
+
|
|
3
|
+
TypeScript SDK with TanStack Query hooks for the RouteFlow API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @routeflow/sdk @tanstack/react-query
|
|
9
|
+
# or
|
|
10
|
+
yarn add @routeflow/sdk @tanstack/react-query
|
|
11
|
+
# or
|
|
12
|
+
pnpm add @routeflow/sdk @tanstack/react-query
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Setup
|
|
16
|
+
|
|
17
|
+
### 1. Configure the SDK
|
|
18
|
+
|
|
19
|
+
Configure the SDK at your app's entry point:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// app/providers.tsx or similar
|
|
23
|
+
import { configureSDK } from '@routeflow/sdk';
|
|
24
|
+
|
|
25
|
+
configureSDK({
|
|
26
|
+
baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001',
|
|
27
|
+
getAccessToken: () => {
|
|
28
|
+
if (typeof window !== 'undefined') {
|
|
29
|
+
return localStorage.getItem('accessToken');
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
},
|
|
33
|
+
refreshAccessToken: async () => {
|
|
34
|
+
// Implement token refresh logic
|
|
35
|
+
const refreshToken = localStorage.getItem('refreshToken');
|
|
36
|
+
if (!refreshToken) return null;
|
|
37
|
+
|
|
38
|
+
const response = await fetch('/api/auth/refresh', {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
body: JSON.stringify({ refreshToken }),
|
|
41
|
+
});
|
|
42
|
+
const data = await response.json();
|
|
43
|
+
localStorage.setItem('accessToken', data.accessToken);
|
|
44
|
+
return data.accessToken;
|
|
45
|
+
},
|
|
46
|
+
onUnauthorized: () => {
|
|
47
|
+
// Redirect to login
|
|
48
|
+
window.location.href = '/login';
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 2. Set up TanStack Query Provider
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// app/providers.tsx
|
|
57
|
+
'use client';
|
|
58
|
+
|
|
59
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
60
|
+
import { useState } from 'react';
|
|
61
|
+
|
|
62
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
63
|
+
const [queryClient] = useState(() => new QueryClient({
|
|
64
|
+
defaultOptions: {
|
|
65
|
+
queries: {
|
|
66
|
+
staleTime: 60 * 1000, // 1 minute
|
|
67
|
+
retry: 1,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
}));
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<QueryClientProvider client={queryClient}>
|
|
74
|
+
{children}
|
|
75
|
+
</QueryClientProvider>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Usage
|
|
81
|
+
|
|
82
|
+
### Query Hooks (GET operations)
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { useRuns, useRun, useDrivers, useCurrentUser } from '@routeflow/sdk';
|
|
86
|
+
|
|
87
|
+
// Get paginated list of runs
|
|
88
|
+
function RunsList() {
|
|
89
|
+
const { data, isLoading, error } = useRuns({
|
|
90
|
+
status: 'IN_PROGRESS',
|
|
91
|
+
page: 1,
|
|
92
|
+
limit: 10,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
if (isLoading) return <Loading />;
|
|
96
|
+
if (error) return <Error message={error.message} />;
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<ul>
|
|
100
|
+
{data?.data.map(run => (
|
|
101
|
+
<li key={run.id}>{run.name}</li>
|
|
102
|
+
))}
|
|
103
|
+
</ul>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Get single run
|
|
108
|
+
function RunDetail({ runId }: { runId: string }) {
|
|
109
|
+
const { data: run, isLoading } = useRun(runId);
|
|
110
|
+
// ...
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Get current user
|
|
114
|
+
function Profile() {
|
|
115
|
+
const { data: user, isLoading } = useCurrentUser();
|
|
116
|
+
// ...
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Get drivers
|
|
120
|
+
function DriversList() {
|
|
121
|
+
const { data: drivers } = useDrivers({ onlineOnly: true });
|
|
122
|
+
// ...
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Mutation Hooks (POST/PUT/DELETE operations)
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import {
|
|
130
|
+
useCreateRun,
|
|
131
|
+
useUpdateRun,
|
|
132
|
+
useDeleteRun,
|
|
133
|
+
useAssignDriver,
|
|
134
|
+
useLogin,
|
|
135
|
+
useLogout,
|
|
136
|
+
} from '@routeflow/sdk';
|
|
137
|
+
|
|
138
|
+
// Create a run
|
|
139
|
+
function CreateRunForm() {
|
|
140
|
+
const { mutate: createRun, isPending } = useCreateRun({
|
|
141
|
+
onSuccess: (run) => {
|
|
142
|
+
console.log('Created run:', run.id);
|
|
143
|
+
router.push(`/runs/${run.id}`);
|
|
144
|
+
},
|
|
145
|
+
onError: (error) => {
|
|
146
|
+
toast.error(error.message);
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const handleSubmit = (data: CreateRunRequest) => {
|
|
151
|
+
createRun(data);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
return <form onSubmit={handleSubmit}>...</form>;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Update a run
|
|
158
|
+
function EditRunForm({ runId }: { runId: string }) {
|
|
159
|
+
const { mutate: updateRun, isPending } = useUpdateRun();
|
|
160
|
+
|
|
161
|
+
const handleSave = (data: UpdateRunRequest) => {
|
|
162
|
+
updateRun({ id: runId, data });
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Delete a run
|
|
167
|
+
function DeleteRunButton({ runId }: { runId: string }) {
|
|
168
|
+
const { mutate: deleteRun, isPending } = useDeleteRun({
|
|
169
|
+
onSuccess: () => router.push('/runs'),
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<button onClick={() => deleteRun(runId)} disabled={isPending}>
|
|
174
|
+
Delete
|
|
175
|
+
</button>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Assign driver
|
|
180
|
+
function AssignDriverSelect({ runId }: { runId: string }) {
|
|
181
|
+
const { mutate: assignDriver } = useAssignDriver();
|
|
182
|
+
|
|
183
|
+
return (
|
|
184
|
+
<select onChange={(e) => assignDriver({ runId, driverId: e.target.value })}>
|
|
185
|
+
...
|
|
186
|
+
</select>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Login
|
|
191
|
+
function LoginForm() {
|
|
192
|
+
const { mutate: login, isPending, error } = useLogin({
|
|
193
|
+
onSuccess: (data) => {
|
|
194
|
+
localStorage.setItem('accessToken', data.accessToken);
|
|
195
|
+
localStorage.setItem('refreshToken', data.refreshToken);
|
|
196
|
+
router.push('/dashboard');
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<form onSubmit={(e) => {
|
|
202
|
+
e.preventDefault();
|
|
203
|
+
login({ email, password });
|
|
204
|
+
}}>
|
|
205
|
+
...
|
|
206
|
+
</form>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Tracking (Public API)
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
import { useTrackingStatus, useTrackingLocation } from '@routeflow/sdk';
|
|
215
|
+
|
|
216
|
+
// Public tracking page
|
|
217
|
+
function TrackingPage({ token }: { token: string }) {
|
|
218
|
+
const { data: status, isLoading: statusLoading } = useTrackingStatus(token);
|
|
219
|
+
const { data: location, isLoading: locationLoading } = useTrackingLocation(token);
|
|
220
|
+
|
|
221
|
+
if (statusLoading) return <Loading />;
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<div>
|
|
225
|
+
<h1>{status?.runName}</h1>
|
|
226
|
+
<p>Status: {status?.status}</p>
|
|
227
|
+
<p>ETA: {status?.eta?.estimatedTime}</p>
|
|
228
|
+
{location?.available && (
|
|
229
|
+
<Map lat={location.location.lat} lng={location.location.lng} />
|
|
230
|
+
)}
|
|
231
|
+
</div>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Available Hooks
|
|
237
|
+
|
|
238
|
+
### Queries
|
|
239
|
+
- `useCurrentUser()` - Get authenticated user
|
|
240
|
+
- `useRuns(params?)` - List runs with pagination/filters
|
|
241
|
+
- `useRun(id)` - Get single run
|
|
242
|
+
- `useDrivers(params?)` - List drivers
|
|
243
|
+
- `useDriver(id)` - Get single driver
|
|
244
|
+
- `useTrackingStatus(token)` - Public tracking status
|
|
245
|
+
- `useTrackingLocation(token)` - Public driver location
|
|
246
|
+
|
|
247
|
+
### Mutations
|
|
248
|
+
- `useLogin()` - Login
|
|
249
|
+
- `useRegister()` - Register
|
|
250
|
+
- `useLogout()` - Logout
|
|
251
|
+
- `useCreateRun()` - Create run
|
|
252
|
+
- `useUpdateRun()` - Update run
|
|
253
|
+
- `useDeleteRun()` - Delete run
|
|
254
|
+
- `useAssignDriver()` - Assign driver to run
|
|
255
|
+
- `useUnassignDriver()` - Remove driver from run
|
|
256
|
+
- `useStartRun()` - Start run
|
|
257
|
+
- `useCompleteRun()` - Complete run
|
|
258
|
+
- `useOptimizeRoute()` - Optimize run route
|
|
259
|
+
- `useCreateStop()` - Add stop to run
|
|
260
|
+
- `useUpdateStop()` - Update stop
|
|
261
|
+
- `useDeleteStop()` - Delete stop
|
|
262
|
+
- `useUpdateStopStatus()` - Update stop status
|
|
263
|
+
- `useReorderStops()` - Reorder stops
|
|
264
|
+
|
|
265
|
+
## Types
|
|
266
|
+
|
|
267
|
+
All types are re-exported from `@routeflow/types`:
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
import type {
|
|
271
|
+
Run,
|
|
272
|
+
Stop,
|
|
273
|
+
Driver,
|
|
274
|
+
User,
|
|
275
|
+
RunStatus,
|
|
276
|
+
StopStatus,
|
|
277
|
+
ApiResponse,
|
|
278
|
+
PaginatedResponse,
|
|
279
|
+
} from '@routeflow/sdk';
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Query Keys
|
|
283
|
+
|
|
284
|
+
For manual cache invalidation:
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
import { queryKeys } from '@routeflow/sdk';
|
|
288
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
289
|
+
|
|
290
|
+
const queryClient = useQueryClient();
|
|
291
|
+
|
|
292
|
+
// Invalidate all runs
|
|
293
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.runs.all });
|
|
294
|
+
|
|
295
|
+
// Invalidate specific run
|
|
296
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.runs.detail('run-123') });
|
|
297
|
+
|
|
298
|
+
// Invalidate all drivers
|
|
299
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.drivers.all });
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## License
|
|
303
|
+
|
|
304
|
+
MIT
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { AxiosInstance, AxiosRequestConfig } from 'axios';
|
|
2
|
+
/**
|
|
3
|
+
* SDK Configuration
|
|
4
|
+
*/
|
|
5
|
+
export interface SDKConfig {
|
|
6
|
+
/** Base URL for the API (e.g., https://api.routeflow.app) */
|
|
7
|
+
baseURL: string;
|
|
8
|
+
/** Function to get the current access token */
|
|
9
|
+
getAccessToken: () => string | null;
|
|
10
|
+
/** Function to refresh the access token when expired */
|
|
11
|
+
refreshAccessToken?: () => Promise<string | null>;
|
|
12
|
+
/** Callback when authentication fails (e.g., redirect to login) */
|
|
13
|
+
onUnauthorized?: () => void;
|
|
14
|
+
/** Request timeout in milliseconds */
|
|
15
|
+
timeout?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Configure the SDK with your API settings
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* configureSDK({
|
|
23
|
+
* baseURL: 'https://api.routeflow.app',
|
|
24
|
+
* getAccessToken: () => localStorage.getItem('accessToken'),
|
|
25
|
+
* refreshAccessToken: async () => {
|
|
26
|
+
* const response = await fetch('/api/refresh');
|
|
27
|
+
* const data = await response.json();
|
|
28
|
+
* return data.accessToken;
|
|
29
|
+
* },
|
|
30
|
+
* onUnauthorized: () => {
|
|
31
|
+
* window.location.href = '/login';
|
|
32
|
+
* },
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare function configureSDK(sdkConfig: SDKConfig): void;
|
|
37
|
+
/**
|
|
38
|
+
* Get the configured axios instance
|
|
39
|
+
* @throws Error if SDK is not configured
|
|
40
|
+
*/
|
|
41
|
+
export declare function getApiClient(): AxiosInstance;
|
|
42
|
+
/**
|
|
43
|
+
* Get the current SDK configuration
|
|
44
|
+
*/
|
|
45
|
+
export declare function getSDKConfig(): SDKConfig | null;
|
|
46
|
+
/**
|
|
47
|
+
* Check if SDK is configured
|
|
48
|
+
*/
|
|
49
|
+
export declare function isSDKConfigured(): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Type-safe API request helper
|
|
52
|
+
*/
|
|
53
|
+
export declare function apiRequest<T>(method: 'get' | 'post' | 'put' | 'patch' | 'delete', url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T>;
|
|
54
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAA0C,MAAM,OAAO,CAAC;AAEvG;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,cAAc,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IACpC,wDAAwD;IACxD,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAClD,mEAAmE;IACnE,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,sCAAsC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAKD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAGvD;AAED;;;GAGG;AACH,wBAAgB,YAAY,IAAI,aAAa,CAO5C;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,SAAS,GAAG,IAAI,CAE/C;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AA8DD;;GAEG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAChC,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,EACnD,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,OAAO,EACd,MAAM,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,CAAC,CAAC,CAWZ"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.configureSDK = configureSDK;
|
|
7
|
+
exports.getApiClient = getApiClient;
|
|
8
|
+
exports.getSDKConfig = getSDKConfig;
|
|
9
|
+
exports.isSDKConfigured = isSDKConfigured;
|
|
10
|
+
exports.apiRequest = apiRequest;
|
|
11
|
+
const axios_1 = __importDefault(require("axios"));
|
|
12
|
+
let config = null;
|
|
13
|
+
let axiosInstance = null;
|
|
14
|
+
/**
|
|
15
|
+
* Configure the SDK with your API settings
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* configureSDK({
|
|
20
|
+
* baseURL: 'https://api.routeflow.app',
|
|
21
|
+
* getAccessToken: () => localStorage.getItem('accessToken'),
|
|
22
|
+
* refreshAccessToken: async () => {
|
|
23
|
+
* const response = await fetch('/api/refresh');
|
|
24
|
+
* const data = await response.json();
|
|
25
|
+
* return data.accessToken;
|
|
26
|
+
* },
|
|
27
|
+
* onUnauthorized: () => {
|
|
28
|
+
* window.location.href = '/login';
|
|
29
|
+
* },
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
function configureSDK(sdkConfig) {
|
|
34
|
+
config = sdkConfig;
|
|
35
|
+
axiosInstance = createAxiosInstance(sdkConfig);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Get the configured axios instance
|
|
39
|
+
* @throws Error if SDK is not configured
|
|
40
|
+
*/
|
|
41
|
+
function getApiClient() {
|
|
42
|
+
if (!axiosInstance) {
|
|
43
|
+
throw new Error('RouteFlow SDK not configured. Call configureSDK() before using the SDK.');
|
|
44
|
+
}
|
|
45
|
+
return axiosInstance;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get the current SDK configuration
|
|
49
|
+
*/
|
|
50
|
+
function getSDKConfig() {
|
|
51
|
+
return config;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Check if SDK is configured
|
|
55
|
+
*/
|
|
56
|
+
function isSDKConfigured() {
|
|
57
|
+
return config !== null && axiosInstance !== null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Create the axios instance with interceptors
|
|
61
|
+
*/
|
|
62
|
+
function createAxiosInstance(sdkConfig) {
|
|
63
|
+
const instance = axios_1.default.create({
|
|
64
|
+
baseURL: sdkConfig.baseURL,
|
|
65
|
+
timeout: sdkConfig.timeout ?? 30000,
|
|
66
|
+
headers: {
|
|
67
|
+
'Content-Type': 'application/json',
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
// Request interceptor - add auth token
|
|
71
|
+
instance.interceptors.request.use((requestConfig) => {
|
|
72
|
+
const token = sdkConfig.getAccessToken();
|
|
73
|
+
if (token) {
|
|
74
|
+
requestConfig.headers.Authorization = `Bearer ${token}`;
|
|
75
|
+
}
|
|
76
|
+
return requestConfig;
|
|
77
|
+
}, (error) => Promise.reject(error));
|
|
78
|
+
// Response interceptor - handle token refresh
|
|
79
|
+
instance.interceptors.response.use((response) => response, async (error) => {
|
|
80
|
+
const originalRequest = error.config;
|
|
81
|
+
// Handle 401 Unauthorized
|
|
82
|
+
if (error.response?.status === 401 && !originalRequest._retry) {
|
|
83
|
+
originalRequest._retry = true;
|
|
84
|
+
// Try to refresh the token
|
|
85
|
+
if (sdkConfig.refreshAccessToken) {
|
|
86
|
+
try {
|
|
87
|
+
const newToken = await sdkConfig.refreshAccessToken();
|
|
88
|
+
if (newToken && originalRequest.headers) {
|
|
89
|
+
originalRequest.headers.Authorization = `Bearer ${newToken}`;
|
|
90
|
+
return instance(originalRequest);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Refresh failed
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Call unauthorized handler
|
|
98
|
+
if (sdkConfig.onUnauthorized) {
|
|
99
|
+
sdkConfig.onUnauthorized();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return Promise.reject(error);
|
|
103
|
+
});
|
|
104
|
+
return instance;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Type-safe API request helper
|
|
108
|
+
*/
|
|
109
|
+
async function apiRequest(method, url, data, config) {
|
|
110
|
+
const client = getApiClient();
|
|
111
|
+
const response = await client.request({
|
|
112
|
+
method,
|
|
113
|
+
url,
|
|
114
|
+
data,
|
|
115
|
+
...config,
|
|
116
|
+
});
|
|
117
|
+
return response.data;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":";;;;;AAyCA,oCAGC;AAMD,oCAOC;AAKD,oCAEC;AAKD,0CAEC;AAiED,gCAgBC;AAxJD,kDAA0B;AAmB1B,IAAI,MAAM,GAAqB,IAAI,CAAC;AACpC,IAAI,aAAa,GAAyB,IAAI,CAAC;AAE/C;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAgB,YAAY,CAAC,SAAoB;IAC/C,MAAM,GAAG,SAAS,CAAC;IACnB,aAAa,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;AACjD,CAAC;AAED;;;GAGG;AACH,SAAgB,YAAY;IAC1B,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CACb,yEAAyE,CAC1E,CAAC;IACJ,CAAC;IACD,OAAO,aAAa,CAAC;AACvB,CAAC;AAED;;GAEG;AACH,SAAgB,YAAY;IAC1B,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,SAAgB,eAAe;IAC7B,OAAO,MAAM,KAAK,IAAI,IAAI,aAAa,KAAK,IAAI,CAAC;AACnD,CAAC;AAED;;GAEG;AACH,SAAS,mBAAmB,CAAC,SAAoB;IAC/C,MAAM,QAAQ,GAAG,eAAK,CAAC,MAAM,CAAC;QAC5B,OAAO,EAAE,SAAS,CAAC,OAAO;QAC1B,OAAO,EAAE,SAAS,CAAC,OAAO,IAAI,KAAK;QACnC,OAAO,EAAE;YACP,cAAc,EAAE,kBAAkB;SACnC;KACF,CAAC,CAAC;IAEH,uCAAuC;IACvC,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CAC/B,CAAC,aAAyC,EAAE,EAAE;QAC5C,MAAM,KAAK,GAAG,SAAS,CAAC,cAAc,EAAE,CAAC;QACzC,IAAI,KAAK,EAAE,CAAC;YACV,aAAa,CAAC,OAAO,CAAC,aAAa,GAAG,UAAU,KAAK,EAAE,CAAC;QAC1D,CAAC;QACD,OAAO,aAAa,CAAC;IACvB,CAAC,EACD,CAAC,KAAiB,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAC7C,CAAC;IAEF,8CAA8C;IAC9C,QAAQ,CAAC,YAAY,CAAC,QAAQ,CAAC,GAAG,CAChC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,EACtB,KAAK,EAAE,KAAiB,EAAE,EAAE;QAC1B,MAAM,eAAe,GAAG,KAAK,CAAC,MAAmD,CAAC;QAElF,0BAA0B;QAC1B,IAAI,KAAK,CAAC,QAAQ,EAAE,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,CAAC;YAC9D,eAAe,CAAC,MAAM,GAAG,IAAI,CAAC;YAE9B,2BAA2B;YAC3B,IAAI,SAAS,CAAC,kBAAkB,EAAE,CAAC;gBACjC,IAAI,CAAC;oBACH,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,kBAAkB,EAAE,CAAC;oBACtD,IAAI,QAAQ,IAAI,eAAe,CAAC,OAAO,EAAE,CAAC;wBACxC,eAAe,CAAC,OAAO,CAAC,aAAa,GAAG,UAAU,QAAQ,EAAE,CAAC;wBAC7D,OAAO,QAAQ,CAAC,eAAe,CAAC,CAAC;oBACnC,CAAC;gBACH,CAAC;gBAAC,MAAM,CAAC;oBACP,iBAAiB;gBACnB,CAAC;YACH,CAAC;YAED,4BAA4B;YAC5B,IAAI,SAAS,CAAC,cAAc,EAAE,CAAC;gBAC7B,SAAS,CAAC,cAAc,EAAE,CAAC;YAC7B,CAAC;QACH,CAAC;QAED,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC,CACF,CAAC;IAEF,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;GAEG;AACI,KAAK,UAAU,UAAU,CAC9B,MAAmD,EACnD,GAAW,EACX,IAAc,EACd,MAA2B;IAE3B,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAE9B,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,OAAO,CAAI;QACvC,MAAM;QACN,GAAG;QACH,IAAI;QACJ,GAAG,MAAM;KACV,CAAC,CAAC;IAEH,OAAO,QAAQ,CAAC,IAAI,CAAC;AACvB,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { useMutation } from '@tanstack/react-query';
|
|
2
|
+
import type { UseMutationOptions } from '@tanstack/react-query';
|
|
3
|
+
import type { LoginRequest, LoginResponse, RegisterRequest } from '../types';
|
|
4
|
+
type LoginMutationOptions = Omit<UseMutationOptions<LoginResponse, Error, LoginRequest, unknown>, 'mutationFn'>;
|
|
5
|
+
type RegisterMutationOptions = Omit<UseMutationOptions<LoginResponse, Error, RegisterRequest, unknown>, 'mutationFn'>;
|
|
6
|
+
type LogoutMutationOptions = Omit<UseMutationOptions<void, Error, void, unknown>, 'mutationFn'>;
|
|
7
|
+
/**
|
|
8
|
+
* Hook for login mutation
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* const { mutate: login, isPending } = useLogin({
|
|
13
|
+
* onSuccess: (data) => {
|
|
14
|
+
* localStorage.setItem('accessToken', data.accessToken);
|
|
15
|
+
* },
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* login({ email: 'user@example.com', password: 'password' });
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function useLogin(options?: LoginMutationOptions): ReturnType<typeof useMutation<LoginResponse, Error, LoginRequest, unknown>>;
|
|
22
|
+
/**
|
|
23
|
+
* Hook for register mutation
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const { mutate: register, isPending } = useRegister({
|
|
28
|
+
* onSuccess: (data) => {
|
|
29
|
+
* localStorage.setItem('accessToken', data.accessToken);
|
|
30
|
+
* },
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* register({
|
|
34
|
+
* email: 'user@example.com',
|
|
35
|
+
* password: 'password',
|
|
36
|
+
* name: 'John Doe',
|
|
37
|
+
* organizationName: 'Acme Inc',
|
|
38
|
+
* });
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare function useRegister(options?: RegisterMutationOptions): ReturnType<typeof useMutation<LoginResponse, Error, RegisterRequest, unknown>>;
|
|
42
|
+
/**
|
|
43
|
+
* Hook for logout mutation
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* const { mutate: logout, isPending } = useLogout({
|
|
48
|
+
* onSuccess: () => {
|
|
49
|
+
* localStorage.removeItem('accessToken');
|
|
50
|
+
* window.location.href = '/login';
|
|
51
|
+
* },
|
|
52
|
+
* });
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare function useLogout(options?: LogoutMutationOptions): ReturnType<typeof useMutation<void, Error, void, unknown>>;
|
|
56
|
+
export {};
|
|
57
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/api/mutations/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAkB,MAAM,uBAAuB,CAAC;AACpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAGhE,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,eAAe,EAAQ,MAAM,UAAU,CAAC;AAuBnF,KAAK,oBAAoB,GAAG,IAAI,CAC9B,kBAAkB,CAAC,aAAa,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,CAAC,EAC/D,YAAY,CACb,CAAC;AAEF,KAAK,uBAAuB,GAAG,IAAI,CACjC,kBAAkB,CAAC,aAAa,EAAE,KAAK,EAAE,eAAe,EAAE,OAAO,CAAC,EAClE,YAAY,CACb,CAAC;AAEF,KAAK,qBAAqB,GAAG,IAAI,CAC/B,kBAAkB,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,EAC9C,YAAY,CACb,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,QAAQ,CACtB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,UAAU,CAAC,OAAO,WAAW,CAAC,aAAa,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC,CAa7E;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CACzB,OAAO,CAAC,EAAE,uBAAuB,GAChC,UAAU,CAAC,OAAO,WAAW,CAAC,aAAa,EAAE,KAAK,EAAE,eAAe,EAAE,OAAO,CAAC,CAAC,CAYhF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CACvB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,UAAU,CAAC,OAAO,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAW5D"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useLogin = useLogin;
|
|
4
|
+
exports.useRegister = useRegister;
|
|
5
|
+
exports.useLogout = useLogout;
|
|
6
|
+
const react_query_1 = require("@tanstack/react-query");
|
|
7
|
+
const client_1 = require("../client");
|
|
8
|
+
const types_1 = require("../types");
|
|
9
|
+
/**
|
|
10
|
+
* Login mutation
|
|
11
|
+
*/
|
|
12
|
+
async function login(credentials) {
|
|
13
|
+
return (0, client_1.apiRequest)('post', '/api/auth/login', credentials);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Register mutation
|
|
17
|
+
*/
|
|
18
|
+
async function register(data) {
|
|
19
|
+
return (0, client_1.apiRequest)('post', '/api/auth/register', data);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Logout mutation
|
|
23
|
+
*/
|
|
24
|
+
async function logout() {
|
|
25
|
+
return (0, client_1.apiRequest)('post', '/api/auth/logout');
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Hook for login mutation
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* const { mutate: login, isPending } = useLogin({
|
|
33
|
+
* onSuccess: (data) => {
|
|
34
|
+
* localStorage.setItem('accessToken', data.accessToken);
|
|
35
|
+
* },
|
|
36
|
+
* });
|
|
37
|
+
*
|
|
38
|
+
* login({ email: 'user@example.com', password: 'password' });
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
function useLogin(options) {
|
|
42
|
+
const queryClient = (0, react_query_1.useQueryClient)();
|
|
43
|
+
return (0, react_query_1.useMutation)({
|
|
44
|
+
mutationFn: login,
|
|
45
|
+
onSettled: (data) => {
|
|
46
|
+
// Update the current user in cache on success
|
|
47
|
+
if (data) {
|
|
48
|
+
queryClient.setQueryData(types_1.queryKeys.auth.me(), data.user);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
...options,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Hook for register mutation
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* const { mutate: register, isPending } = useRegister({
|
|
60
|
+
* onSuccess: (data) => {
|
|
61
|
+
* localStorage.setItem('accessToken', data.accessToken);
|
|
62
|
+
* },
|
|
63
|
+
* });
|
|
64
|
+
*
|
|
65
|
+
* register({
|
|
66
|
+
* email: 'user@example.com',
|
|
67
|
+
* password: 'password',
|
|
68
|
+
* name: 'John Doe',
|
|
69
|
+
* organizationName: 'Acme Inc',
|
|
70
|
+
* });
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
function useRegister(options) {
|
|
74
|
+
const queryClient = (0, react_query_1.useQueryClient)();
|
|
75
|
+
return (0, react_query_1.useMutation)({
|
|
76
|
+
mutationFn: register,
|
|
77
|
+
onSettled: (data) => {
|
|
78
|
+
if (data) {
|
|
79
|
+
queryClient.setQueryData(types_1.queryKeys.auth.me(), data.user);
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
...options,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Hook for logout mutation
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* const { mutate: logout, isPending } = useLogout({
|
|
91
|
+
* onSuccess: () => {
|
|
92
|
+
* localStorage.removeItem('accessToken');
|
|
93
|
+
* window.location.href = '/login';
|
|
94
|
+
* },
|
|
95
|
+
* });
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
function useLogout(options) {
|
|
99
|
+
const queryClient = (0, react_query_1.useQueryClient)();
|
|
100
|
+
return (0, react_query_1.useMutation)({
|
|
101
|
+
mutationFn: logout,
|
|
102
|
+
onSettled: () => {
|
|
103
|
+
// Clear all queries
|
|
104
|
+
queryClient.clear();
|
|
105
|
+
},
|
|
106
|
+
...options,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/api/mutations/auth.ts"],"names":[],"mappings":";;AAwDA,4BAeC;AAqBD,kCAcC;AAeD,8BAaC;AAtID,uDAAoE;AAEpE,sCAAuC;AACvC,oCAAqC;AAGrC;;GAEG;AACH,KAAK,UAAU,KAAK,CAAC,WAAyB;IAC5C,OAAO,IAAA,mBAAU,EAAgB,MAAM,EAAE,iBAAiB,EAAE,WAAW,CAAC,CAAC;AAC3E,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,QAAQ,CAAC,IAAqB;IAC3C,OAAO,IAAA,mBAAU,EAAgB,MAAM,EAAE,oBAAoB,EAAE,IAAI,CAAC,CAAC;AACvE,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,MAAM;IACnB,OAAO,IAAA,mBAAU,EAAO,MAAM,EAAE,kBAAkB,CAAC,CAAC;AACtD,CAAC;AAiBD;;;;;;;;;;;;;GAaG;AACH,SAAgB,QAAQ,CACtB,OAA8B;IAE9B,MAAM,WAAW,GAAG,IAAA,4BAAc,GAAE,CAAC;IAErC,OAAO,IAAA,yBAAW,EAAC;QACjB,UAAU,EAAE,KAAK;QACjB,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE;YAClB,8CAA8C;YAC9C,IAAI,IAAI,EAAE,CAAC;gBACT,WAAW,CAAC,YAAY,CAAO,iBAAS,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YACjE,CAAC;QACH,CAAC;QACD,GAAG,OAAO;KACX,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAgB,WAAW,CACzB,OAAiC;IAEjC,MAAM,WAAW,GAAG,IAAA,4BAAc,GAAE,CAAC;IAErC,OAAO,IAAA,yBAAW,EAAC;QACjB,UAAU,EAAE,QAAQ;QACpB,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE;YAClB,IAAI,IAAI,EAAE,CAAC;gBACT,WAAW,CAAC,YAAY,CAAO,iBAAS,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YACjE,CAAC;QACH,CAAC;QACD,GAAG,OAAO;KACX,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAgB,SAAS,CACvB,OAA+B;IAE/B,MAAM,WAAW,GAAG,IAAA,4BAAc,GAAE,CAAC;IAErC,OAAO,IAAA,yBAAW,EAAC;QACjB,UAAU,EAAE,MAAM;QAClB,SAAS,EAAE,GAAG,EAAE;YACd,oBAAoB;YACpB,WAAW,CAAC,KAAK,EAAE,CAAC;QACtB,CAAC;QACD,GAAG,OAAO;KACX,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mutation hooks - TanStack Query mutations for POST/PUT/PATCH/DELETE operations
|
|
3
|
+
*/
|
|
4
|
+
export { useLogin, useRegister, useLogout } from './auth';
|
|
5
|
+
export { useUpdateUser, useDeleteUser } from './users';
|
|
6
|
+
export { useCreateRun, useUpdateRun, useDeleteRun, useAssignDriver, useUnassignDriver, useStartRun, useCompleteRun, useOptimizeRoute, } from './runs';
|
|
7
|
+
export { useCreateStop, useUpdateStop, useDeleteStop, useUpdateStopStatus, useReorderStops, } from './stops';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/api/mutations/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACvD,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,cAAc,EACd,gBAAgB,GACjB,MAAM,QAAQ,CAAC;AAChB,OAAO,EACL,aAAa,EACb,aAAa,EACb,aAAa,EACb,mBAAmB,EACnB,eAAe,GAChB,MAAM,SAAS,CAAC"}
|