flysoft-react-ui 0.2.0 → 0.2.1

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,89 @@
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
+ */
38
+ get<T = unknown>(url: string, headers?: Record<string, string>): Promise<T>;
39
+ /**
40
+ * Realiza una petición POST
41
+ */
42
+ post<T = unknown>(url: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
43
+ /**
44
+ * Realiza una petición PUT
45
+ */
46
+ put<T = unknown>(url: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
47
+ /**
48
+ * Realiza una petición DELETE
49
+ */
50
+ del<T = unknown>(url: string, headers?: Record<string, string>): Promise<T>;
51
+ /**
52
+ * Obtiene un archivo como Blob
53
+ */
54
+ getFile(url: string, headers?: Record<string, string>): Promise<FileResponse>;
55
+ /**
56
+ * Obtiene un archivo y retorna su URL como objeto
57
+ */
58
+ getFileAsUrl(url: string, headers?: Record<string, string>): Promise<string>;
59
+ /**
60
+ * Abre un archivo en una nueva ventana
61
+ */
62
+ openFile(url: string, headers?: Record<string, string>): Promise<void>;
63
+ /**
64
+ * Descarga un archivo
65
+ */
66
+ downloadFile(url: string, headers?: Record<string, string>): Promise<void>;
67
+ /**
68
+ * Sube uno o más archivos usando FormData
69
+ */
70
+ uploadFile<T = unknown>(url: string, files: FileList | File[], headers?: UploadFileOptions): Promise<T>;
40
71
  }
41
- export declare const apiClient: ApiClient;
42
- export declare const createApiClient: (config?: ApiClientConfig) => ApiClient;
72
+ /**
73
+ * Cliente de API compartido con todas las funciones de HTTP
74
+ */
75
+ export declare const apiClient: ApiClientService;
76
+ /**
77
+ * Crea una nueva instancia del cliente de API
78
+ */
79
+ export declare const createApiClient: (config?: ApiClientConfig) => ApiClientService;
80
+ /**
81
+ * Establece el proveedor de token global para el cliente compartido
82
+ */
43
83
  export declare const setApiClientTokenProvider: (provider: TokenProvider | undefined) => void;
84
+ /**
85
+ * Limpia el proveedor de token global
86
+ */
44
87
  export declare const clearApiClientTokenProvider: () => void;
45
88
  export {};
46
89
  //# 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":"AAAA,OAAc,EAEZ,KAAK,aAAa,EACnB,MAAM,OAAO,CAAC;AAEf,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,KAAK,aAAa,GAAG,MAAM,MAAM,GAAG,SAAS,CAAC;AAE9C,UAAU,YAAY;IACpB,IAAI,EAAE,IAAI,CAAC;IACX,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;CACnC;AAED,UAAU,iBAAiB;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,cAAM,gBAAgB;IACpB,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,aAAa,CAAC,CAAgB;gBAE1B,MAAM,CAAC,EAAE,eAAe;IAapC,OAAO,CAAC,iBAAiB;IA8BzB;;;OAGG;IACH,gBAAgB,CAAC,QAAQ,EAAE,aAAa,GAAG,SAAS,GAAG,IAAI;IAI3D;;OAEG;IACH,kBAAkB,IAAI,IAAI;IAI1B;;OAEG;IACH,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI;YAa/B,YAAY;IAmB1B;;OAEG;IACG,GAAG,CAAC,CAAC,GAAG,OAAO,EACnB,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,OAAO,CAAC,CAAC,CAAC;IASb;;OAEG;IACG,IAAI,CAAC,CAAC,GAAG,OAAO,EACpB,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,OAAO,CAAC,CAAC,CAAC;IAUb;;OAEG;IACG,GAAG,CAAC,CAAC,GAAG,OAAO,EACnB,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,OAAO,CAAC,CAAC,CAAC;IAUb;;OAEG;IACG,GAAG,CAAC,CAAC,GAAG,OAAO,EACnB,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,OAAO,CAAC,CAAC,CAAC;IASb;;OAEG;IACG,OAAO,CACX,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GACnC,OAAO,CAAC,YAAY,CAAC;IAWxB;;OAEG;IACG,YAAY,CAChB,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GACnC,OAAO,CAAC,MAAM,CAAC;IAMlB;;OAEG;IACG,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAM5E;;OAEG;IACG,YAAY,CAChB,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,OAAO,CAAC,IAAI,CAAC;IAmBhB;;OAEG;IACG,UAAU,CAAC,CAAC,GAAG,OAAO,EAC1B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,QAAQ,GAAG,IAAI,EAAE,EACxB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,CAAC,CAAC;CAed;AAKD;;GAEG;AACH,eAAO,MAAM,SAAS,kBAAe,CAAC;AAEtC;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,SAAS,eAAe,KAAG,gBAE1D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,yBAAyB,GACpC,UAAU,aAAa,GAAG,SAAS,KAClC,IAEF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,2BAA2B,QAAO,IAE9C,CAAC"}
@@ -1,58 +1,57 @@
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 && typeof config.headers.set === "function") {
23
+ config.headers.set("Authorization", `Bearer ${token}`);
37
24
  }
38
25
  else {
39
- const existingHeaders = requestConfig.headers ?? {};
40
- requestConfig.headers = {
41
- ...existingHeaders,
42
- Authorization: `Bearer ${token}`,
43
- };
26
+ const headers = config.headers;
27
+ headers.Authorization = `Bearer ${token}`;
44
28
  }
45
29
  }
46
- return requestConfig;
30
+ return config;
31
+ }, (error) => {
32
+ return Promise.reject(error);
33
+ });
34
+ // Response interceptor para manejo de errores (opcional, puede extenderse)
35
+ this.instance.interceptors.response.use((response) => response, (error) => {
36
+ return Promise.reject(error);
47
37
  });
48
- this.instance.interceptors.response.use((response) => response, (error) => Promise.reject(this.toAppApiError(error)));
49
38
  }
39
+ /**
40
+ * Establece el proveedor de token que se usará en todas las peticiones
41
+ * @param provider Función que retorna el token de autorización
42
+ */
50
43
  setTokenProvider(provider) {
51
44
  this.tokenProvider = provider;
52
45
  }
46
+ /**
47
+ * Limpia el proveedor de token
48
+ */
53
49
  clearTokenProvider() {
54
50
  this.tokenProvider = undefined;
55
51
  }
52
+ /**
53
+ * Actualiza la configuración por defecto del cliente
54
+ */
56
55
  updateDefaults(config) {
57
56
  if (config.baseURL) {
58
57
  this.instance.defaults.baseURL = config.baseURL;
@@ -61,127 +60,148 @@ export class ApiClient {
61
60
  this.instance.defaults.timeout = config.timeout;
62
61
  }
63
62
  if (config.headers) {
64
- this.instance.defaults.headers = {
65
- ...this.instance.defaults.headers,
66
- ...config.headers,
67
- };
63
+ // Actualizar headers comunes de forma segura
64
+ Object.assign(this.instance.defaults.headers.common || {}, config.headers);
68
65
  }
69
66
  }
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
- }
67
+ async axiosRequest({ method, url, headers, body, }) {
68
+ return await this.instance({
69
+ method,
70
+ headers,
71
+ url,
72
+ data: body,
73
+ });
121
74
  }
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
- }
75
+ /**
76
+ * Realiza una petición GET
77
+ */
78
+ async get(url, headers) {
79
+ const response = await this.axiosRequest({
80
+ method: "GET",
81
+ url,
82
+ headers,
83
+ });
84
+ return response.data;
134
85
  }
135
- transformResponse(response) {
86
+ /**
87
+ * Realiza una petición POST
88
+ */
89
+ async post(url, body, headers) {
90
+ const response = await this.axiosRequest({
91
+ method: "POST",
92
+ url,
93
+ headers,
94
+ body,
95
+ });
136
96
  return response.data;
137
97
  }
138
- ensureAppApiError(error) {
139
- if (this.isAppApiError(error)) {
140
- return error;
141
- }
142
- return this.toAppApiError(error);
98
+ /**
99
+ * Realiza una petición PUT
100
+ */
101
+ async put(url, body, headers) {
102
+ const response = await this.axiosRequest({
103
+ method: "PUT",
104
+ url,
105
+ headers,
106
+ body,
107
+ });
108
+ return response.data;
143
109
  }
144
- isAppApiError(error) {
145
- return (isObject(error) &&
146
- typeof error.message === "string" &&
147
- ("code" in error || "status" in error || "details" in error));
110
+ /**
111
+ * Realiza una petición DELETE
112
+ */
113
+ async del(url, headers) {
114
+ const response = await this.axiosRequest({
115
+ method: "DELETE",
116
+ url,
117
+ headers,
118
+ });
119
+ return response.data;
148
120
  }
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
- }
121
+ /**
122
+ * Obtiene un archivo como Blob
123
+ */
124
+ async getFile(url, headers = {}) {
125
+ const response = await this.instance.get(url, {
126
+ responseType: "blob",
127
+ headers,
128
+ });
172
129
  return {
173
- message: "Unexpected error",
174
- code: "UNKNOWN_ERROR",
175
- details: error,
130
+ data: response.data,
131
+ headers: response.headers,
176
132
  };
177
133
  }
134
+ /**
135
+ * Obtiene un archivo y retorna su URL como objeto
136
+ */
137
+ async getFileAsUrl(url, headers = {}) {
138
+ const { data } = await this.getFile(url, headers);
139
+ const blob = new Blob([data], { type: data.type });
140
+ return URL.createObjectURL(blob);
141
+ }
142
+ /**
143
+ * Abre un archivo en una nueva ventana
144
+ */
145
+ async openFile(url, headers) {
146
+ const { data } = await this.getFile(url, headers);
147
+ const urlData = URL.createObjectURL(data);
148
+ window.open(urlData);
149
+ }
150
+ /**
151
+ * Descarga un archivo
152
+ */
153
+ async downloadFile(url, headers) {
154
+ const { data, headers: dataHeaders } = await this.getFile(url, headers);
155
+ const contentDisposition = dataHeaders["content-disposition"] || dataHeaders["Content-Disposition"];
156
+ const fileName = contentDisposition
157
+ ?.split("filename=")[1]
158
+ ?.split(";")[0]
159
+ .replaceAll('"', "");
160
+ const blob = new Blob([data], { type: data.type });
161
+ const link = document.createElement("a");
162
+ link.href = window.URL.createObjectURL(blob);
163
+ link.setAttribute("download", fileName || "");
164
+ document.body.appendChild(link);
165
+ link.click();
166
+ link.parentNode?.removeChild(link);
167
+ }
168
+ /**
169
+ * Sube uno o más archivos usando FormData
170
+ */
171
+ async uploadFile(url, files, headers) {
172
+ const formData = new FormData();
173
+ const { paramName = "file", ...newHeaders } = headers || {};
174
+ const fileArray = Array.from(files);
175
+ for (const file of fileArray) {
176
+ formData.append(paramName, file, file.name);
177
+ }
178
+ const response = await this.instance.post(url, formData, {
179
+ headers: newHeaders,
180
+ });
181
+ return response.data;
182
+ }
178
183
  }
179
- const sharedClient = new ApiClient();
184
+ // Instancia compartida del cliente
185
+ const sharedClient = new ApiClientService();
186
+ /**
187
+ * Cliente de API compartido con todas las funciones de HTTP
188
+ */
180
189
  export const apiClient = sharedClient;
181
- export const createApiClient = (config) => new ApiClient(config);
190
+ /**
191
+ * Crea una nueva instancia del cliente de API
192
+ */
193
+ export const createApiClient = (config) => {
194
+ return new ApiClientService(config);
195
+ };
196
+ /**
197
+ * Establece el proveedor de token global para el cliente compartido
198
+ */
182
199
  export const setApiClientTokenProvider = (provider) => {
183
200
  sharedClient.setTokenProvider(provider);
184
201
  };
202
+ /**
203
+ * Limpia el proveedor de token global
204
+ */
185
205
  export const clearApiClientTokenProvider = () => {
186
206
  sharedClient.clearTokenProvider();
187
207
  };
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.1",
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"}