flysoft-react-ui 0.2.0 → 0.2.2

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.
@@ -1,46 +1,92 @@
1
- import type { AxiosRequestConfig } from "axios";
2
- export type HttpMethod = "get" | "post" | "put" | "delete" | "patch";
3
- export interface AppApiError {
4
- message: string;
5
- code?: string;
6
- status?: number;
7
- details?: unknown;
8
- originalError?: unknown;
9
- }
1
+ import { type AxiosResponse } from "axios";
10
2
  export interface ApiClientConfig {
11
3
  baseURL?: string;
12
4
  timeout?: number;
13
5
  headers?: Record<string, string>;
14
6
  }
15
- export interface RequestOptions<TBody = unknown, TParams = Record<string, unknown>> {
16
- url: string;
17
- params?: TParams;
18
- data?: TBody;
19
- headers?: Record<string, string>;
20
- config?: AxiosRequestConfig;
21
- }
22
7
  type TokenProvider = () => string | undefined;
23
- export declare class ApiClient {
24
- private readonly instance;
8
+ interface FileResponse {
9
+ data: Blob;
10
+ headers: AxiosResponse["headers"];
11
+ }
12
+ interface UploadFileOptions {
13
+ paramName?: string;
14
+ [key: string]: unknown;
15
+ }
16
+ declare class ApiClientService {
17
+ private instance;
25
18
  private tokenProvider?;
26
19
  constructor(config?: ApiClientConfig);
27
20
  private setupInterceptors;
21
+ /**
22
+ * Establece el proveedor de token que se usará en todas las peticiones
23
+ * @param provider Función que retorna el token de autorización
24
+ */
28
25
  setTokenProvider(provider: TokenProvider | undefined): void;
26
+ /**
27
+ * Limpia el proveedor de token
28
+ */
29
29
  clearTokenProvider(): void;
30
+ /**
31
+ * Actualiza la configuración por defecto del cliente
32
+ */
30
33
  updateDefaults(config: ApiClientConfig): void;
31
- get<TResponse, TParams = Record<string, unknown>>(options: RequestOptions<never, TParams>): Promise<TResponse>;
32
- delete<TResponse, TParams = Record<string, unknown>>(options: RequestOptions<never, TParams>): Promise<TResponse>;
33
- post<TResponse, TBody = unknown, TParams = Record<string, unknown>>(options: RequestOptions<TBody, TParams>): Promise<TResponse>;
34
- put<TResponse, TBody = unknown, TParams = Record<string, unknown>>(options: RequestOptions<TBody, TParams>): Promise<TResponse>;
35
- patch<TResponse, TBody = unknown, TParams = Record<string, unknown>>(options: RequestOptions<TBody, TParams>): Promise<TResponse>;
36
- private transformResponse;
37
- private ensureAppApiError;
38
- private isAppApiError;
39
- private toAppApiError;
34
+ private axiosRequest;
35
+ /**
36
+ * Realiza una petición GET
37
+ * @param url URL del endpoint
38
+ * @param params Parámetros opcionales que se enviarán como query string
39
+ * @param headers Headers opcionales de la petición
40
+ */
41
+ get<T = unknown>(url: string, params?: Record<string, unknown>, headers?: Record<string, string>): Promise<T>;
42
+ /**
43
+ * Realiza una petición POST
44
+ */
45
+ post<T = unknown>(url: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
46
+ /**
47
+ * Realiza una petición PUT
48
+ */
49
+ put<T = unknown>(url: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
50
+ /**
51
+ * Realiza una petición DELETE
52
+ */
53
+ del<T = unknown>(url: string, headers?: Record<string, string>): Promise<T>;
54
+ /**
55
+ * Obtiene un archivo como Blob
56
+ */
57
+ getFile(url: string, headers?: Record<string, string>): Promise<FileResponse>;
58
+ /**
59
+ * Obtiene un archivo y retorna su URL como objeto
60
+ */
61
+ getFileAsUrl(url: string, headers?: Record<string, string>): Promise<string>;
62
+ /**
63
+ * Abre un archivo en una nueva ventana
64
+ */
65
+ openFile(url: string, headers?: Record<string, string>): Promise<void>;
66
+ /**
67
+ * Descarga un archivo
68
+ */
69
+ downloadFile(url: string, headers?: Record<string, string>): Promise<void>;
70
+ /**
71
+ * Sube uno o más archivos usando FormData
72
+ */
73
+ uploadFile<T = unknown>(url: string, files: FileList | File[], headers?: UploadFileOptions): Promise<T>;
40
74
  }
41
- export declare const apiClient: ApiClient;
42
- export declare const createApiClient: (config?: ApiClientConfig) => ApiClient;
75
+ /**
76
+ * Cliente de API compartido con todas las funciones de HTTP
77
+ */
78
+ export declare const apiClient: ApiClientService;
79
+ /**
80
+ * Crea una nueva instancia del cliente de API
81
+ */
82
+ export declare const createApiClient: (config?: ApiClientConfig) => ApiClientService;
83
+ /**
84
+ * Establece el proveedor de token global para el cliente compartido
85
+ */
43
86
  export declare const setApiClientTokenProvider: (provider: TokenProvider | undefined) => void;
87
+ /**
88
+ * Limpia el proveedor de token global
89
+ */
44
90
  export declare const clearApiClientTokenProvider: () => void;
45
91
  export {};
46
92
  //# sourceMappingURL=apiClient.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"apiClient.d.ts","sourceRoot":"","sources":["../../src/services/apiClient.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAGV,kBAAkB,EAGnB,MAAM,OAAO,CAAC;AAEf,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;AAErE,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,cAAc,CAC7B,KAAK,GAAG,OAAO,EACf,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAEjC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,KAAK,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,kBAAkB,CAAC;CAC7B;AAED,KAAK,aAAa,GAAG,MAAM,MAAM,GAAG,SAAS,CAAC;AAoB9C,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC,OAAO,CAAC,aAAa,CAAC,CAAgB;gBAE1B,MAAM,CAAC,EAAE,eAAe;IAcpC,OAAO,CAAC,iBAAiB;IAwBzB,gBAAgB,CAAC,QAAQ,EAAE,aAAa,GAAG,SAAS;IAIpD,kBAAkB;IAIlB,cAAc,CAAC,MAAM,EAAE,eAAe;IAehC,GAAG,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpD,OAAO,EAAE,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,GACtC,OAAO,CAAC,SAAS,CAAC;IAaf,MAAM,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACvD,OAAO,EAAE,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,GACtC,OAAO,CAAC,SAAS,CAAC;IAaf,IAAI,CACR,SAAS,EACT,KAAK,GAAG,OAAO,EACf,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,OAAO,EAAE,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC;IAaxD,GAAG,CACP,SAAS,EACT,KAAK,GAAG,OAAO,EACf,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,OAAO,EAAE,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC;IAaxD,KAAK,CACT,SAAS,EACT,KAAK,GAAG,OAAO,EACf,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,OAAO,EAAE,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC;IAa9D,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,aAAa;CAiCtB;AAID,eAAO,MAAM,SAAS,WAAe,CAAC;AAEtC,eAAO,MAAM,eAAe,GAAI,SAAS,eAAe,cAA0B,CAAC;AAEnF,eAAO,MAAM,yBAAyB,GAAI,UAAU,aAAa,GAAG,SAAS,SAE5E,CAAC;AAEF,eAAO,MAAM,2BAA2B,YAEvC,CAAC"}
1
+ {"version":3,"file":"apiClient.d.ts","sourceRoot":"","sources":["../../src/services/apiClient.ts"],"names":[],"mappings":"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"}
@@ -1,58 +1,58 @@
1
- import axios from "axios";
2
- const DEFAULT_CONFIG = {
3
- baseURL: "/api",
4
- timeout: 15000,
5
- };
6
- const isObject = (value) => typeof value === "object" && value !== null;
7
- const extractErrorData = (error) => {
8
- if (isObject(error.response?.data)) {
9
- return error.response?.data;
10
- }
11
- if (typeof error.response?.data === "string") {
12
- return { message: error.response?.data };
13
- }
14
- return {};
15
- };
16
- export class ApiClient {
1
+ import axios, {} from "axios";
2
+ class ApiClientService {
17
3
  instance;
18
4
  tokenProvider;
19
5
  constructor(config) {
20
6
  this.instance = axios.create({
21
- baseURL: config?.baseURL ?? DEFAULT_CONFIG.baseURL,
22
- timeout: config?.timeout ?? DEFAULT_CONFIG.timeout,
7
+ baseURL: config?.baseURL ?? "",
8
+ timeout: config?.timeout ?? 15000,
23
9
  headers: {
24
10
  "Content-Type": "application/json",
25
- Accept: "application/json",
26
11
  ...config?.headers,
27
12
  },
28
13
  });
29
14
  this.setupInterceptors();
30
15
  }
31
16
  setupInterceptors() {
32
- this.instance.interceptors.request.use((requestConfig) => {
17
+ // Request interceptor para inyectar el token automáticamente
18
+ this.instance.interceptors.request.use((config) => {
33
19
  const token = this.tokenProvider?.();
34
- if (token) {
35
- if (requestConfig.headers && "set" in requestConfig.headers) {
36
- requestConfig.headers.set?.("Authorization", `Bearer ${token}`);
20
+ if (token && config.headers) {
21
+ // Manejo compatible con diferentes versiones de axios
22
+ if ("set" in config.headers &&
23
+ typeof config.headers.set === "function") {
24
+ config.headers.set("Authorization", `Bearer ${token}`);
37
25
  }
38
26
  else {
39
- const existingHeaders = requestConfig.headers ?? {};
40
- requestConfig.headers = {
41
- ...existingHeaders,
42
- Authorization: `Bearer ${token}`,
43
- };
27
+ const headers = config.headers;
28
+ headers.Authorization = `Bearer ${token}`;
44
29
  }
45
30
  }
46
- return requestConfig;
31
+ return config;
32
+ }, (error) => {
33
+ return Promise.reject(error);
34
+ });
35
+ // Response interceptor para manejo de errores (opcional, puede extenderse)
36
+ this.instance.interceptors.response.use((response) => response, (error) => {
37
+ return Promise.reject(error);
47
38
  });
48
- this.instance.interceptors.response.use((response) => response, (error) => Promise.reject(this.toAppApiError(error)));
49
39
  }
40
+ /**
41
+ * Establece el proveedor de token que se usará en todas las peticiones
42
+ * @param provider Función que retorna el token de autorización
43
+ */
50
44
  setTokenProvider(provider) {
51
45
  this.tokenProvider = provider;
52
46
  }
47
+ /**
48
+ * Limpia el proveedor de token
49
+ */
53
50
  clearTokenProvider() {
54
51
  this.tokenProvider = undefined;
55
52
  }
53
+ /**
54
+ * Actualiza la configuración por defecto del cliente
55
+ */
56
56
  updateDefaults(config) {
57
57
  if (config.baseURL) {
58
58
  this.instance.defaults.baseURL = config.baseURL;
@@ -61,127 +61,153 @@ export class ApiClient {
61
61
  this.instance.defaults.timeout = config.timeout;
62
62
  }
63
63
  if (config.headers) {
64
- this.instance.defaults.headers = {
65
- ...this.instance.defaults.headers,
66
- ...config.headers,
67
- };
64
+ // Actualizar headers comunes de forma segura
65
+ Object.assign(this.instance.defaults.headers.common || {}, config.headers);
68
66
  }
69
67
  }
70
- async get(options) {
71
- try {
72
- const response = await this.instance.get(options.url, {
73
- params: options.params,
74
- headers: options.headers,
75
- ...options.config,
76
- });
77
- return this.transformResponse(response);
78
- }
79
- catch (error) {
80
- throw this.ensureAppApiError(error);
81
- }
82
- }
83
- async delete(options) {
84
- try {
85
- const response = await this.instance.delete(options.url, {
86
- params: options.params,
87
- headers: options.headers,
88
- ...options.config,
89
- });
90
- return this.transformResponse(response);
91
- }
92
- catch (error) {
93
- throw this.ensureAppApiError(error);
94
- }
95
- }
96
- async post(options) {
97
- try {
98
- const response = await this.instance.post(options.url, options.data, {
99
- params: options.params,
100
- headers: options.headers,
101
- ...options.config,
102
- });
103
- return this.transformResponse(response);
104
- }
105
- catch (error) {
106
- throw this.ensureAppApiError(error);
107
- }
108
- }
109
- async put(options) {
110
- try {
111
- const response = await this.instance.put(options.url, options.data, {
112
- params: options.params,
113
- headers: options.headers,
114
- ...options.config,
115
- });
116
- return this.transformResponse(response);
117
- }
118
- catch (error) {
119
- throw this.ensureAppApiError(error);
120
- }
68
+ async axiosRequest({ method, url, headers, body, params, }) {
69
+ return await this.instance({
70
+ method,
71
+ headers,
72
+ url,
73
+ data: body,
74
+ params,
75
+ });
121
76
  }
122
- async patch(options) {
123
- try {
124
- const response = await this.instance.patch(options.url, options.data, {
125
- params: options.params,
126
- headers: options.headers,
127
- ...options.config,
128
- });
129
- return this.transformResponse(response);
130
- }
131
- catch (error) {
132
- throw this.ensureAppApiError(error);
133
- }
77
+ /**
78
+ * Realiza una petición GET
79
+ * @param url URL del endpoint
80
+ * @param params Parámetros opcionales que se enviarán como query string
81
+ * @param headers Headers opcionales de la petición
82
+ */
83
+ async get(url, params, headers) {
84
+ const response = await this.axiosRequest({
85
+ method: "GET",
86
+ url,
87
+ params,
88
+ headers,
89
+ });
90
+ return response.data;
134
91
  }
135
- transformResponse(response) {
92
+ /**
93
+ * Realiza una petición POST
94
+ */
95
+ async post(url, body, headers) {
96
+ const response = await this.axiosRequest({
97
+ method: "POST",
98
+ url,
99
+ headers,
100
+ body,
101
+ });
136
102
  return response.data;
137
103
  }
138
- ensureAppApiError(error) {
139
- if (this.isAppApiError(error)) {
140
- return error;
141
- }
142
- return this.toAppApiError(error);
104
+ /**
105
+ * Realiza una petición PUT
106
+ */
107
+ async put(url, body, headers) {
108
+ const response = await this.axiosRequest({
109
+ method: "PUT",
110
+ url,
111
+ headers,
112
+ body,
113
+ });
114
+ return response.data;
143
115
  }
144
- isAppApiError(error) {
145
- return (isObject(error) &&
146
- typeof error.message === "string" &&
147
- ("code" in error || "status" in error || "details" in error));
116
+ /**
117
+ * Realiza una petición DELETE
118
+ */
119
+ async del(url, headers) {
120
+ const response = await this.axiosRequest({
121
+ method: "DELETE",
122
+ url,
123
+ headers,
124
+ });
125
+ return response.data;
148
126
  }
149
- toAppApiError(error) {
150
- if (axios.isAxiosError(error)) {
151
- const extracted = extractErrorData(error);
152
- return {
153
- message: extracted.message ??
154
- error.message ??
155
- "Unexpected API error",
156
- code: extracted.code ??
157
- error.code ??
158
- "API_ERROR",
159
- status: error.response?.status ?? error.status,
160
- details: extracted,
161
- originalError: error,
162
- };
163
- }
164
- if (error instanceof Error) {
165
- return {
166
- message: error.message,
167
- code: "UNKNOWN_ERROR",
168
- details: undefined,
169
- originalError: error,
170
- };
171
- }
127
+ /**
128
+ * Obtiene un archivo como Blob
129
+ */
130
+ async getFile(url, headers = {}) {
131
+ const response = await this.instance.get(url, {
132
+ responseType: "blob",
133
+ headers,
134
+ });
172
135
  return {
173
- message: "Unexpected error",
174
- code: "UNKNOWN_ERROR",
175
- details: error,
136
+ data: response.data,
137
+ headers: response.headers,
176
138
  };
177
139
  }
140
+ /**
141
+ * Obtiene un archivo y retorna su URL como objeto
142
+ */
143
+ async getFileAsUrl(url, headers = {}) {
144
+ const { data } = await this.getFile(url, headers);
145
+ const blob = new Blob([data], { type: data.type });
146
+ return URL.createObjectURL(blob);
147
+ }
148
+ /**
149
+ * Abre un archivo en una nueva ventana
150
+ */
151
+ async openFile(url, headers) {
152
+ const { data } = await this.getFile(url, headers);
153
+ const urlData = URL.createObjectURL(data);
154
+ window.open(urlData);
155
+ }
156
+ /**
157
+ * Descarga un archivo
158
+ */
159
+ async downloadFile(url, headers) {
160
+ const { data, headers: dataHeaders } = await this.getFile(url, headers);
161
+ const contentDisposition = dataHeaders["content-disposition"] || dataHeaders["Content-Disposition"];
162
+ const fileName = contentDisposition
163
+ ?.split("filename=")[1]
164
+ ?.split(";")[0]
165
+ .replaceAll('"', "");
166
+ const blob = new Blob([data], { type: data.type });
167
+ const link = document.createElement("a");
168
+ link.href = window.URL.createObjectURL(blob);
169
+ link.setAttribute("download", fileName || "");
170
+ document.body.appendChild(link);
171
+ link.click();
172
+ link.parentNode?.removeChild(link);
173
+ }
174
+ /**
175
+ * Sube uno o más archivos usando FormData
176
+ */
177
+ async uploadFile(url, files, headers) {
178
+ const formData = new FormData();
179
+ const { paramName = "file", ...newHeaders } = headers || {};
180
+ const fileArray = Array.from(files);
181
+ for (const file of fileArray) {
182
+ formData.append(paramName, file, file.name);
183
+ }
184
+ const response = await this.instance.post(url, formData, {
185
+ headers: newHeaders,
186
+ });
187
+ return response.data;
188
+ }
178
189
  }
179
- const sharedClient = new ApiClient();
190
+ // Instancia compartida del cliente
191
+ const sharedClient = new ApiClientService();
192
+ /**
193
+ * Cliente de API compartido con todas las funciones de HTTP
194
+ */
180
195
  export const apiClient = sharedClient;
181
- export const createApiClient = (config) => new ApiClient(config);
196
+ /**
197
+ * Crea una nueva instancia del cliente de API
198
+ */
199
+ export const createApiClient = (config) => {
200
+ return new ApiClientService(config);
201
+ };
202
+ /**
203
+ * Establece el proveedor de token global para el cliente compartido
204
+ */
182
205
  export const setApiClientTokenProvider = (provider) => {
183
206
  sharedClient.setTokenProvider(provider);
184
207
  };
208
+ /**
209
+ * Limpia el proveedor de token global
210
+ */
185
211
  export const clearApiClientTokenProvider = () => {
186
212
  sharedClient.clearTokenProvider();
187
213
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "flysoft-react-ui",
3
3
  "private": false,
4
- "version": "0.2.0",
4
+ "version": "0.2.2",
5
5
  "type": "module",
6
6
  "description": "A modern React UI component library with Tailwind CSS, TypeScript, and FontAwesome 5. Includes forms, layouts, themes, and templates for rapid development.",
7
7
  "keywords": [
@@ -1,4 +0,0 @@
1
- import React from "react";
2
- declare const ApiClientDocs: React.FC;
3
- export default ApiClientDocs;
4
- //# sourceMappingURL=ApiClientDocs.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ApiClientDocs.d.ts","sourceRoot":"","sources":["../../src/docs/ApiClientDocs.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAgC,MAAM,OAAO,CAAC;AAarD,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAiO1B,CAAC;AAEF,eAAe,aAAa,CAAC"}
@@ -1,66 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React, { useCallback, useState } from "react";
3
- import { Badge, Button, Card, Input, useApi, useApiMutation, } from "../index";
4
- const API_BASE = "https://jsonplaceholder.typicode.com";
5
- const ApiClientDocs = () => {
6
- const [postId, setPostId] = useState("1");
7
- const [formState, setFormState] = useState({
8
- title: "",
9
- body: "",
10
- });
11
- const { data: postData, loading: loadingPost, error: postError, refetch: refetchPost, } = useApi({
12
- url: `${API_BASE}/posts/${postId || 1}`,
13
- method: "get",
14
- enabled: false,
15
- queryKey: "docs.posts.detail",
16
- onError: (error) => {
17
- console.warn("Example query error", error);
18
- },
19
- });
20
- const { data: createdPost, loading: creatingPost, error: createError, mutateAsync: createPost, reset: resetMutation, } = useApiMutation({
21
- url: `${API_BASE}/posts`,
22
- method: "post",
23
- invalidateKeys: ["docs.posts.detail"],
24
- onSuccess: () => {
25
- // Invalidate re-executes the query registered under the same key.
26
- setFormState({ title: "", body: "" });
27
- },
28
- });
29
- const handleFetchPost = useCallback(() => {
30
- void refetchPost();
31
- }, [refetchPost]);
32
- const handleSubmit = useCallback(async (event) => {
33
- event.preventDefault();
34
- try {
35
- await createPost({
36
- data: {
37
- title: formState.title,
38
- body: formState.body,
39
- userId: 1,
40
- },
41
- });
42
- }
43
- catch (error) {
44
- console.info("Example mutation error", error);
45
- }
46
- }, [createPost, formState.body, formState.title]);
47
- const renderErrorBadge = (error) => {
48
- if (!error)
49
- return null;
50
- return (_jsx(Badge, { variant: "danger", icon: "fa-exclamation-triangle", children: error.message }));
51
- };
52
- return (_jsxs("div", { className: "max-w-5xl mx-auto space-y-8", children: [_jsx(Card, { title: "ApiClient - Resumen", children: _jsxs("div", { className: "space-y-4", children: [_jsxs("p", { style: { color: "var(--flysoft-text-secondary)" }, children: ["La capa de networking usa ", _jsx("code", { children: "axios" }), " con interceptores listos para inyectar tokens y normalizar errores en", _jsx("code", { children: "AppApiError" }), ". Importa los estilos con", _jsx("code", { children: "import \"flysoft-react-ui/styles\";" }), " y consume", _jsx("code", { children: "apiClient" }), ",", _jsx("code", { children: "useApi" }), " y ", _jsx("code", { children: "useApiMutation" }), " directamente desde la librer\u00EDa."] }), _jsxs("ul", { className: "list-disc list-inside space-y-2 text-sm", children: [_jsxs("li", { children: ["M\u00E9todos disponibles: ", _jsx("code", { children: "get" }), ", ", _jsx("code", { children: "post" }), ",", _jsx("code", { children: "put" }), ", ", _jsx("code", { children: "delete" }), " y ", _jsx("code", { children: "patch" }), " con opciones tipadas."] }), _jsxs("li", { children: ["Los errores se entregan como ", _jsx("code", { children: "AppApiError" }), " para que los componentes usen badges, alerts u otros patrones ya disponibles sin tocar los hooks."] }), _jsxs("li", { children: ["Usa ", _jsx("code", { children: "setApiClientTokenProvider" }), " para inyectar tokens (placeholder listo para integrarse con tu ThemeProvider o contextos existentes)."] })] })] }) }), _jsx(Card, { title: "Consulta simple con useApi", children: _jsxs("div", { className: "space-y-6", children: [_jsxs("form", { className: "grid grid-cols-1 sm:grid-cols-[1fr_auto] gap-4 items-end", onSubmit: (event) => {
53
- event.preventDefault();
54
- handleFetchPost();
55
- }, children: [_jsx(Input, { label: "ID del post", icon: "fa-hashtag", value: postId, onChange: (event) => setPostId(event.target.value) }), _jsx(Button, { type: "submit", variant: "primary", icon: "fa-search", loading: loadingPost, children: "Consultar" })] }), _jsxs("div", { className: "space-y-3", children: [loadingPost && (_jsx(Badge, { variant: "info", icon: "fa-spinner", className: "animate-pulse", children: "Cargando post..." })), renderErrorBadge(postError), postData && !loadingPost && (_jsx(Card, { title: `Post ${postData.id}`, children: _jsxs("div", { className: "space-y-2", children: [_jsx("h4", { className: "text-lg font-semibold", style: { color: "var(--flysoft-text-primary)" }, children: postData.title }), _jsx("p", { style: { color: "var(--flysoft-text-secondary)" }, children: postData.body })] }) }))] })] }) }), _jsx(Card, { title: "Mutaci\u00F3n con useApiMutation", children: _jsxs("form", { className: "space-y-6", onSubmit: handleSubmit, children: [_jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-4", children: [_jsx(Input, { label: "T\u00EDtulo", icon: "fa-heading", value: formState.title, onChange: (event) => setFormState((prev) => ({
56
- ...prev,
57
- title: event.target.value,
58
- })) }), _jsx(Input, { label: "Contenido", icon: "fa-align-left", value: formState.body, onChange: (event) => setFormState((prev) => ({
59
- ...prev,
60
- body: event.target.value,
61
- })) })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [_jsx(Button, { type: "submit", variant: "primary", icon: "fa-paper-plane", loading: creatingPost, children: "Enviar datos" }), _jsx(Button, { type: "button", variant: "outline", icon: "fa-undo", onClick: () => {
62
- setFormState({ title: "", body: "" });
63
- resetMutation();
64
- }, children: "Reiniciar" })] }), _jsxs("div", { className: "space-y-3", children: [creatingPost && (_jsx(Badge, { variant: "info", icon: "fa-spinner", className: "animate-pulse", children: "Enviando..." })), renderErrorBadge(createError), createdPost && !creatingPost && (_jsxs(Badge, { variant: "success", icon: "fa-check-circle", children: ["Post #", createdPost.id, " creado (ejemplo)."] })), _jsxs("p", { className: "text-sm", style: { color: "var(--flysoft-text-secondary)" }, children: ["Usa ", _jsx("code", { children: "invalidateKeys" }), " para refrescar queries que hayan sido registradas con ", _jsx("code", { children: "queryKey" }), " en ", _jsx("code", { children: "useApi" }), ". Esto permite mantener badges y alerts sincronizados con el estado de la UI sin alterar los hooks."] })] })] }) })] }));
65
- };
66
- export default ApiClientDocs;
@@ -1,27 +0,0 @@
1
- import type { AxiosRequestConfig } from "axios";
2
- import { type AppApiError, type HttpMethod, type RequestOptions } from "../services/apiClient";
3
- type AnyRequestOptions = RequestOptions<unknown, Record<string, unknown>>;
4
- type QueryRefetcher = (override?: Partial<AnyRequestOptions>) => Promise<unknown>;
5
- export declare const registerQuery: (key: string, refetcher: QueryRefetcher) => void;
6
- export declare const unregisterQuery: (key: string, refetcher: QueryRefetcher) => void;
7
- export declare const invalidateQueries: (keys: string[]) => Promise<void>;
8
- type CleanHttpMethod = HttpMethod;
9
- export interface UseApiOptions<TResponse, TParams = Record<string, unknown>, TBody = unknown, TTransformed = TResponse> extends Omit<RequestOptions<TBody, TParams>, "config"> {
10
- method?: CleanHttpMethod;
11
- transform?: (response: TResponse) => TTransformed;
12
- enabled?: boolean;
13
- queryKey?: string;
14
- config?: AxiosRequestConfig;
15
- onSuccess?: (data: TTransformed) => void;
16
- onError?: (error: AppApiError) => void;
17
- }
18
- export interface UseApiResult<TData, TBody = unknown, TParams = Record<string, unknown>> {
19
- data: TData | undefined;
20
- loading: boolean;
21
- error: AppApiError | undefined;
22
- refetch: (override?: Partial<RequestOptions<TBody, TParams>>) => Promise<TData>;
23
- reset: () => void;
24
- }
25
- export declare const useApi: <TResponse, TParams = Record<string, unknown>, TBody = unknown, TTransformed = TResponse>(options: UseApiOptions<TResponse, TParams, TBody, TTransformed>) => UseApiResult<TTransformed, TBody, TParams>;
26
- export {};
27
- //# sourceMappingURL=useApi.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useApi.d.ts","sourceRoot":"","sources":["../../src/hooks/useApi.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,OAAO,CAAC;AAChD,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,cAAc,EACpB,MAAM,uBAAuB,CAAC;AAE/B,KAAK,iBAAiB,GAAG,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAE1E,KAAK,cAAc,GAAG,CACpB,QAAQ,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,KAClC,OAAO,CAAC,OAAO,CAAC,CAAC;AAStB,eAAO,MAAM,aAAa,GAAI,KAAK,MAAM,EAAE,WAAW,cAAc,SAKnE,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,KAAK,MAAM,EAAE,WAAW,cAAc,SAOrE,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAU,MAAM,MAAM,EAAE,kBAgBrD,CAAC;AAEF,KAAK,eAAe,GAAG,UAAU,CAAC;AAElC,MAAM,WAAW,aAAa,CAC5B,SAAS,EACT,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,KAAK,GAAG,OAAO,EACf,YAAY,GAAG,SAAS,CACxB,SAAQ,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC;IACtD,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,SAAS,KAAK,YAAY,CAAC;IAClD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACzC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;CACxC;AAED,MAAM,WAAW,YAAY,CAC3B,KAAK,EACL,KAAK,GAAG,OAAO,EACf,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAEjC,IAAI,EAAE,KAAK,GAAG,SAAS,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,WAAW,GAAG,SAAS,CAAC;IAC/B,OAAO,EAAE,CACP,QAAQ,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,KAC/C,OAAO,CAAC,KAAK,CAAC,CAAC;IACpB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAqDD,eAAO,MAAM,MAAM,GACjB,SAAS,EACT,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,KAAK,GAAG,OAAO,EACf,YAAY,GAAG,SAAS,EAExB,SAAS,aAAa,CAAC,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,CAAC,KAC9D,YAAY,CAAC,YAAY,EAAE,KAAK,EAAE,OAAO,CAyG3C,CAAC"}