@djangocfg/ext-payments 1.0.8 → 1.0.10

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/dist/index.d.cts CHANGED
@@ -279,6 +279,8 @@ interface HttpRequest {
279
279
  params?: Record<string, any>;
280
280
  /** FormData for file uploads (multipart/form-data) */
281
281
  formData?: FormData;
282
+ /** Binary data for octet-stream uploads */
283
+ binaryBody?: Blob | ArrayBuffer;
282
284
  }
283
285
  interface HttpResponse<T = any> {
284
286
  data: T;
@@ -531,11 +533,13 @@ declare class APIClient {
531
533
  private httpClient;
532
534
  private logger;
533
535
  private retryConfig;
536
+ private tokenGetter;
534
537
  ext_payments_payments: ExtPaymentsPayments;
535
538
  constructor(baseUrl: string, options?: {
536
539
  httpClient?: HttpClientAdapter;
537
540
  loggerConfig?: Partial<LoggerConfig>;
538
541
  retryConfig?: RetryConfig;
542
+ tokenGetter?: () => string | null;
539
543
  });
540
544
  /**
541
545
  * Get CSRF token from cookies (for SessionAuthentication).
@@ -543,6 +547,15 @@ declare class APIClient {
543
547
  * Returns null if cookie doesn't exist (JWT-only auth).
544
548
  */
545
549
  getCsrfToken(): string | null;
550
+ /**
551
+ * Get the base URL for building streaming/download URLs.
552
+ */
553
+ getBaseUrl(): string;
554
+ /**
555
+ * Get JWT token for URL authentication (used in streaming endpoints).
556
+ * Returns null if no token getter is configured or no token is available.
557
+ */
558
+ getToken(): string | null;
546
559
  /**
547
560
  * Make HTTP request with Django CSRF and session handling.
548
561
  * Automatically retries on network errors and 5xx server errors.
@@ -551,6 +564,7 @@ declare class APIClient {
551
564
  params?: Record<string, any>;
552
565
  body?: any;
553
566
  formData?: FormData;
567
+ binaryBody?: Blob | ArrayBuffer;
554
568
  headers?: Record<string, string>;
555
569
  }): Promise<T>;
556
570
  /**
@@ -715,7 +729,7 @@ declare const PaymentDetailSchema: z.ZodObject<{
715
729
  status_display: z.ZodString;
716
730
  pay_address: z.ZodNullable<z.ZodString>;
717
731
  qr_code_url: z.ZodNullable<z.ZodString>;
718
- payment_url: z.ZodNullable<z.ZodURL>;
732
+ payment_url: z.ZodNullable<z.ZodUnion<readonly [z.ZodURL, z.ZodLiteral<"">]>>;
719
733
  transaction_hash: z.ZodNullable<z.ZodString>;
720
734
  explorer_link: z.ZodNullable<z.ZodString>;
721
735
  confirmations_count: z.ZodInt;
@@ -995,30 +1009,38 @@ declare namespace index {
995
1009
  }
996
1010
 
997
1011
  /**
998
- * Global API Instance - Singleton configuration
1012
+ * Global API Instance - Singleton configuration with auto-configuration support
999
1013
  *
1000
- * This module provides a global API instance that can be configured once
1001
- * and used throughout your application.
1014
+ * This module provides a global API instance that auto-configures from
1015
+ * environment variables or can be configured manually.
1002
1016
  *
1003
- * Usage:
1017
+ * AUTO-CONFIGURATION (recommended):
1018
+ * Set one of these environment variables and the API will auto-configure:
1019
+ * - NEXT_PUBLIC_API_URL (Next.js)
1020
+ * - VITE_API_URL (Vite)
1021
+ * - REACT_APP_API_URL (Create React App)
1022
+ * - API_URL (generic)
1023
+ *
1024
+ * Then just use fetchers and hooks directly:
1025
+ * ```typescript
1026
+ * import { getUsers } from './_utils/fetchers'
1027
+ * const users = await getUsers({ page: 1 })
1028
+ * ```
1029
+ *
1030
+ * MANUAL CONFIGURATION:
1004
1031
  * ```typescript
1005
- * // Configure once (e.g., in your app entry point)
1006
1032
  * import { configureAPI } from './api-instance'
1007
1033
  *
1008
1034
  * configureAPI({
1009
1035
  * baseUrl: 'https://api.example.com',
1010
1036
  * token: 'your-jwt-token'
1011
1037
  * })
1012
- *
1013
- * // Then use fetchers and hooks anywhere without configuration
1014
- * import { getUsers } from './fetchers'
1015
- * const users = await getUsers({ page: 1 })
1016
1038
  * ```
1017
1039
  *
1018
1040
  * For SSR or multiple instances:
1019
1041
  * ```typescript
1020
1042
  * import { API } from './index'
1021
- * import { getUsers } from './fetchers'
1043
+ * import { getUsers } from './_utils/fetchers'
1022
1044
  *
1023
1045
  * const api = new API('https://api.example.com')
1024
1046
  * const users = await getUsers({ page: 1 }, api)
@@ -1027,11 +1049,12 @@ declare namespace index {
1027
1049
 
1028
1050
  /**
1029
1051
  * Get the global API instance
1030
- * @throws Error if API is not configured
1052
+ * Auto-configures from environment variables on first call if not manually configured.
1053
+ * @throws Error if API is not configured and no env variable is set
1031
1054
  */
1032
1055
  declare function getAPIInstance(): API;
1033
1056
  /**
1034
- * Check if API is configured
1057
+ * Check if API is configured (or can be auto-configured)
1035
1058
  */
1036
1059
  declare function isAPIConfigured(): boolean;
1037
1060
  /**
package/dist/index.d.ts CHANGED
@@ -279,6 +279,8 @@ interface HttpRequest {
279
279
  params?: Record<string, any>;
280
280
  /** FormData for file uploads (multipart/form-data) */
281
281
  formData?: FormData;
282
+ /** Binary data for octet-stream uploads */
283
+ binaryBody?: Blob | ArrayBuffer;
282
284
  }
283
285
  interface HttpResponse<T = any> {
284
286
  data: T;
@@ -531,11 +533,13 @@ declare class APIClient {
531
533
  private httpClient;
532
534
  private logger;
533
535
  private retryConfig;
536
+ private tokenGetter;
534
537
  ext_payments_payments: ExtPaymentsPayments;
535
538
  constructor(baseUrl: string, options?: {
536
539
  httpClient?: HttpClientAdapter;
537
540
  loggerConfig?: Partial<LoggerConfig>;
538
541
  retryConfig?: RetryConfig;
542
+ tokenGetter?: () => string | null;
539
543
  });
540
544
  /**
541
545
  * Get CSRF token from cookies (for SessionAuthentication).
@@ -543,6 +547,15 @@ declare class APIClient {
543
547
  * Returns null if cookie doesn't exist (JWT-only auth).
544
548
  */
545
549
  getCsrfToken(): string | null;
550
+ /**
551
+ * Get the base URL for building streaming/download URLs.
552
+ */
553
+ getBaseUrl(): string;
554
+ /**
555
+ * Get JWT token for URL authentication (used in streaming endpoints).
556
+ * Returns null if no token getter is configured or no token is available.
557
+ */
558
+ getToken(): string | null;
546
559
  /**
547
560
  * Make HTTP request with Django CSRF and session handling.
548
561
  * Automatically retries on network errors and 5xx server errors.
@@ -551,6 +564,7 @@ declare class APIClient {
551
564
  params?: Record<string, any>;
552
565
  body?: any;
553
566
  formData?: FormData;
567
+ binaryBody?: Blob | ArrayBuffer;
554
568
  headers?: Record<string, string>;
555
569
  }): Promise<T>;
556
570
  /**
@@ -715,7 +729,7 @@ declare const PaymentDetailSchema: z.ZodObject<{
715
729
  status_display: z.ZodString;
716
730
  pay_address: z.ZodNullable<z.ZodString>;
717
731
  qr_code_url: z.ZodNullable<z.ZodString>;
718
- payment_url: z.ZodNullable<z.ZodURL>;
732
+ payment_url: z.ZodNullable<z.ZodUnion<readonly [z.ZodURL, z.ZodLiteral<"">]>>;
719
733
  transaction_hash: z.ZodNullable<z.ZodString>;
720
734
  explorer_link: z.ZodNullable<z.ZodString>;
721
735
  confirmations_count: z.ZodInt;
@@ -995,30 +1009,38 @@ declare namespace index {
995
1009
  }
996
1010
 
997
1011
  /**
998
- * Global API Instance - Singleton configuration
1012
+ * Global API Instance - Singleton configuration with auto-configuration support
999
1013
  *
1000
- * This module provides a global API instance that can be configured once
1001
- * and used throughout your application.
1014
+ * This module provides a global API instance that auto-configures from
1015
+ * environment variables or can be configured manually.
1002
1016
  *
1003
- * Usage:
1017
+ * AUTO-CONFIGURATION (recommended):
1018
+ * Set one of these environment variables and the API will auto-configure:
1019
+ * - NEXT_PUBLIC_API_URL (Next.js)
1020
+ * - VITE_API_URL (Vite)
1021
+ * - REACT_APP_API_URL (Create React App)
1022
+ * - API_URL (generic)
1023
+ *
1024
+ * Then just use fetchers and hooks directly:
1025
+ * ```typescript
1026
+ * import { getUsers } from './_utils/fetchers'
1027
+ * const users = await getUsers({ page: 1 })
1028
+ * ```
1029
+ *
1030
+ * MANUAL CONFIGURATION:
1004
1031
  * ```typescript
1005
- * // Configure once (e.g., in your app entry point)
1006
1032
  * import { configureAPI } from './api-instance'
1007
1033
  *
1008
1034
  * configureAPI({
1009
1035
  * baseUrl: 'https://api.example.com',
1010
1036
  * token: 'your-jwt-token'
1011
1037
  * })
1012
- *
1013
- * // Then use fetchers and hooks anywhere without configuration
1014
- * import { getUsers } from './fetchers'
1015
- * const users = await getUsers({ page: 1 })
1016
1038
  * ```
1017
1039
  *
1018
1040
  * For SSR or multiple instances:
1019
1041
  * ```typescript
1020
1042
  * import { API } from './index'
1021
- * import { getUsers } from './fetchers'
1043
+ * import { getUsers } from './_utils/fetchers'
1022
1044
  *
1023
1045
  * const api = new API('https://api.example.com')
1024
1046
  * const users = await getUsers({ page: 1 }, api)
@@ -1027,11 +1049,12 @@ declare namespace index {
1027
1049
 
1028
1050
  /**
1029
1051
  * Get the global API instance
1030
- * @throws Error if API is not configured
1052
+ * Auto-configures from environment variables on first call if not manually configured.
1053
+ * @throws Error if API is not configured and no env variable is set
1031
1054
  */
1032
1055
  declare function getAPIInstance(): API;
1033
1056
  /**
1034
- * Check if API is configured
1057
+ * Check if API is configured (or can be auto-configured)
1035
1058
  */
1036
1059
  declare function isAPIConfigured(): boolean;
1037
1060
  /**
package/dist/index.js CHANGED
@@ -1,15 +1,16 @@
1
1
  import { createConsola, consola } from 'consola';
2
2
  import pRetry, { AbortError } from 'p-retry';
3
3
  import { z } from 'zod';
4
- import { createExtensionAPI } from '@djangocfg/ext-base/api';
4
+ import { initializeExtensionAPI, createExtensionAPI } from '@djangocfg/ext-base/api';
5
5
  import { Wallet, CreditCard, History, RefreshCw, Plus, XCircle, ExternalLink, Search, Filter, Clock, AlertCircle, CheckCircle2, ArrowDownLeft, ArrowUpRight } from 'lucide-react';
6
- import { Tabs, TabsList, TabsTrigger, TabsContent, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Form, FormField, FormItem, FormLabel, FormControl, Input, FormDescription, FormMessage, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, TokenIcon, DialogFooter, Button, CopyButton, Card, CardHeader, CardTitle, Skeleton, CardContent, Badge, useDRFPagination, Table, TableHeader, TableRow, TableHead, TableBody, TableCell, StaticPagination } from '@djangocfg/ui-nextjs';
6
+ import { Tabs, TabsList, TabsTrigger, TabsContent, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Form, FormField, FormItem, FormLabel, FormControl, Input, FormDescription, FormMessage, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, TokenIcon, DialogFooter, Button, CopyButton, Card, CardHeader, CardTitle, Skeleton, CardContent, Badge, Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '@djangocfg/ui-core';
7
7
  import { createContext, useState, useMemo, useEffect, useContext } from 'react';
8
8
  import useSWR, { SWRConfig, useSWRConfig } from 'swr';
9
9
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
10
10
  import { useForm } from 'react-hook-form';
11
11
  import { zodResolver } from '@hookform/resolvers/zod';
12
12
  import moment from 'moment';
13
+ import { useDRFPagination, StaticPagination } from '@djangocfg/ui-nextjs/components';
13
14
  import { createExtensionConfig } from '@djangocfg/ext-base';
14
15
 
15
16
  var __defProp = Object.defineProperty;
@@ -120,7 +121,7 @@ var models_exports = {};
120
121
  // src/api/generated/ext_payments/http.ts
121
122
  var FetchAdapter = class {
122
123
  async request(request) {
123
- const { method, url, headers, body, params, formData } = request;
124
+ const { method, url, headers, body, params, formData, binaryBody } = request;
124
125
  let finalUrl = url;
125
126
  if (params) {
126
127
  const searchParams = new URLSearchParams();
@@ -138,6 +139,9 @@ var FetchAdapter = class {
138
139
  let requestBody;
139
140
  if (formData) {
140
141
  requestBody = formData;
142
+ } else if (binaryBody) {
143
+ finalHeaders["Content-Type"] = "application/octet-stream";
144
+ requestBody = binaryBody;
141
145
  } else if (body) {
142
146
  finalHeaders["Content-Type"] = "application/json";
143
147
  requestBody = JSON.stringify(body);
@@ -462,11 +466,13 @@ var APIClient = class {
462
466
  httpClient;
463
467
  logger = null;
464
468
  retryConfig = null;
469
+ tokenGetter = null;
465
470
  // Sub-clients
466
471
  ext_payments_payments;
467
472
  constructor(baseUrl, options) {
468
473
  this.baseUrl = baseUrl.replace(/\/$/, "");
469
474
  this.httpClient = options?.httpClient || new FetchAdapter();
475
+ this.tokenGetter = options?.tokenGetter || null;
470
476
  if (options?.loggerConfig !== void 0) {
471
477
  this.logger = new APILogger(options.loggerConfig);
472
478
  }
@@ -489,6 +495,19 @@ var APIClient = class {
489
495
  }
490
496
  return null;
491
497
  }
498
+ /**
499
+ * Get the base URL for building streaming/download URLs.
500
+ */
501
+ getBaseUrl() {
502
+ return this.baseUrl;
503
+ }
504
+ /**
505
+ * Get JWT token for URL authentication (used in streaming endpoints).
506
+ * Returns null if no token getter is configured or no token is available.
507
+ */
508
+ getToken() {
509
+ return this.tokenGetter ? this.tokenGetter() : null;
510
+ }
492
511
  /**
493
512
  * Make HTTP request with Django CSRF and session handling.
494
513
  * Automatically retries on network errors and 5xx server errors.
@@ -519,7 +538,7 @@ var APIClient = class {
519
538
  const headers = {
520
539
  ...options?.headers || {}
521
540
  };
522
- if (!options?.formData && !headers["Content-Type"]) {
541
+ if (!options?.formData && !options?.binaryBody && !headers["Content-Type"]) {
523
542
  headers["Content-Type"] = "application/json";
524
543
  }
525
544
  if (this.logger) {
@@ -538,7 +557,8 @@ var APIClient = class {
538
557
  headers,
539
558
  params: options?.params,
540
559
  body: options?.body,
541
- formData: options?.formData
560
+ formData: options?.formData,
561
+ binaryBody: options?.binaryBody
542
562
  });
543
563
  const duration = Date.now() - startTime;
544
564
  if (response.status >= 400) {
@@ -874,7 +894,7 @@ var PaymentDetailSchema = z.object({
874
894
  status_display: z.string(),
875
895
  pay_address: z.string().nullable(),
876
896
  qr_code_url: z.string().nullable(),
877
- payment_url: z.url().nullable(),
897
+ payment_url: z.union([z.url(), z.literal("")]).nullable(),
878
898
  transaction_hash: z.string().nullable(),
879
899
  explorer_link: z.string().nullable(),
880
900
  confirmations_count: z.int(),
@@ -959,15 +979,28 @@ __export(fetchers_exports, {
959
979
 
960
980
  // src/api/generated/ext_payments/api-instance.ts
961
981
  var globalAPI = null;
982
+ var autoConfigAttempted = false;
983
+ function tryAutoConfigureFromEnv() {
984
+ if (autoConfigAttempted) return;
985
+ autoConfigAttempted = true;
986
+ if (globalAPI) return;
987
+ if (typeof process === "undefined" || !process.env) return;
988
+ const baseUrl = process.env.NEXT_PUBLIC_API_URL || process.env.VITE_API_URL || process.env.REACT_APP_API_URL || process.env.API_URL;
989
+ if (baseUrl) {
990
+ globalAPI = new API(baseUrl);
991
+ }
992
+ }
962
993
  function getAPIInstance() {
994
+ tryAutoConfigureFromEnv();
963
995
  if (!globalAPI) {
964
996
  throw new Error(
965
- 'API not configured. Call configureAPI() with your base URL before using fetchers or hooks.\n\nExample:\n import { configureAPI } from "./api-instance"\n configureAPI({ baseUrl: "https://api.example.com" })'
997
+ 'API not configured. Call configureAPI() with your base URL before using fetchers or hooks.\n\nExample:\n import { configureAPI } from "./api-instance"\n configureAPI({ baseUrl: "https://api.example.com" })\n\nOr set environment variable: NEXT_PUBLIC_API_URL, VITE_API_URL, or REACT_APP_API_URL'
966
998
  );
967
999
  }
968
1000
  return globalAPI;
969
1001
  }
970
1002
  function isAPIConfigured() {
1003
+ tryAutoConfigureFromEnv();
971
1004
  return globalAPI !== null;
972
1005
  }
973
1006
  function configureAPI(config) {
@@ -1282,7 +1315,8 @@ var API = class {
1282
1315
  this._loadTokensFromStorage();
1283
1316
  this._client = new APIClient(this.baseUrl, {
1284
1317
  retryConfig: this.options?.retryConfig,
1285
- loggerConfig: this.options?.loggerConfig
1318
+ loggerConfig: this.options?.loggerConfig,
1319
+ tokenGetter: () => this.getToken()
1286
1320
  });
1287
1321
  this._injectAuthHeader();
1288
1322
  this.ext_payments_payments = this._client.ext_payments_payments;
@@ -1294,7 +1328,8 @@ var API = class {
1294
1328
  _reinitClients() {
1295
1329
  this._client = new APIClient(this.baseUrl, {
1296
1330
  retryConfig: this.options?.retryConfig,
1297
- loggerConfig: this.options?.loggerConfig
1331
+ loggerConfig: this.options?.loggerConfig,
1332
+ tokenGetter: () => this.getToken()
1298
1333
  });
1299
1334
  this._injectAuthHeader();
1300
1335
  this.ext_payments_payments = this._client.ext_payments_payments;
@@ -1385,6 +1420,7 @@ var API = class {
1385
1420
  return "./schema.json";
1386
1421
  }
1387
1422
  };
1423
+ initializeExtensionAPI(configureAPI);
1388
1424
  var apiPayments = createExtensionAPI(API);
1389
1425
  function usePaymentsBalanceRetrieve(client) {
1390
1426
  return useSWR(
@@ -2552,7 +2588,7 @@ var PaymentsLayout = () => {
2552
2588
  // package.json
2553
2589
  var package_default = {
2554
2590
  name: "@djangocfg/ext-payments",
2555
- version: "1.0.8",
2591
+ version: "1.0.10",
2556
2592
  description: "Payments system extension for DjangoCFG",
2557
2593
  keywords: [
2558
2594
  "django",
@@ -2613,6 +2649,7 @@ var package_default = {
2613
2649
  peerDependencies: {
2614
2650
  "@djangocfg/api": "workspace:*",
2615
2651
  "@djangocfg/ext-base": "workspace:*",
2652
+ "@djangocfg/ui-core": "workspace:*",
2616
2653
  "@djangocfg/ui-nextjs": "workspace:*",
2617
2654
  consola: "^3.4.2",
2618
2655
  "lucide-react": "^0.545.0",
@@ -2627,6 +2664,7 @@ var package_default = {
2627
2664
  "@djangocfg/api": "workspace:*",
2628
2665
  "@djangocfg/ext-base": "workspace:*",
2629
2666
  "@djangocfg/typescript-config": "workspace:*",
2667
+ "@djangocfg/ui-nextjs": "workspace:*",
2630
2668
  "@types/node": "^24.7.2",
2631
2669
  "@types/react": "^19.0.0",
2632
2670
  consola: "^3.4.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ext-payments",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "Payments system extension for DjangoCFG",
5
5
  "keywords": [
6
6
  "django",
@@ -59,9 +59,10 @@
59
59
  "check": "tsc --noEmit"
60
60
  },
61
61
  "peerDependencies": {
62
- "@djangocfg/api": "^2.1.91",
62
+ "@djangocfg/api": "^2.1.107",
63
63
  "@djangocfg/ext-base": "^1.0.8",
64
- "@djangocfg/ui-nextjs": "^2.1.91",
64
+ "@djangocfg/ui-core": "^2.1.107",
65
+ "@djangocfg/ui-nextjs": "^2.1.107",
65
66
  "consola": "^3.4.2",
66
67
  "lucide-react": "^0.545.0",
67
68
  "next": "^15.5.7",
@@ -72,9 +73,10 @@
72
73
  "moment": "^2.30.1"
73
74
  },
74
75
  "devDependencies": {
75
- "@djangocfg/api": "^2.1.91",
76
+ "@djangocfg/api": "^2.1.107",
76
77
  "@djangocfg/ext-base": "^1.0.8",
77
- "@djangocfg/typescript-config": "^2.1.91",
78
+ "@djangocfg/typescript-config": "^2.1.107",
79
+ "@djangocfg/ui-nextjs": "^2.1.107",
78
80
  "@types/node": "^24.7.2",
79
81
  "@types/react": "^19.0.0",
80
82
  "consola": "^3.4.2",
@@ -65,12 +65,5 @@ openapi_client = OpenAPIClientConfig(
65
65
  )
66
66
  ```
67
67
 
68
- **Copy to Next.js** (if `nextjs_admin` configured):
69
- ```python
70
- nextjs_admin = NextJsAdminConfig(
71
- project_path="../frontend/apps/...",
72
- api_output_path="app/_lib/api/generated",
73
- )
74
- ```
75
-
76
68
  @see https://djangocfg.com/docs/features/api-generation
69
+
@@ -25,7 +25,7 @@ export const PaymentDetailSchema = z.object({
25
25
  status_display: z.string(),
26
26
  pay_address: z.string().nullable(),
27
27
  qr_code_url: z.string().nullable(),
28
- payment_url: z.url().nullable(),
28
+ payment_url: z.union([z.url(), z.literal('')]).nullable(),
29
29
  transaction_hash: z.string().nullable(),
30
30
  explorer_link: z.string().nullable(),
31
31
  confirmations_count: z.int(),
@@ -1,29 +1,37 @@
1
1
  // Auto-generated by DjangoCFG - see CLAUDE.md
2
2
  /**
3
- * Global API Instance - Singleton configuration
3
+ * Global API Instance - Singleton configuration with auto-configuration support
4
4
  *
5
- * This module provides a global API instance that can be configured once
6
- * and used throughout your application.
5
+ * This module provides a global API instance that auto-configures from
6
+ * environment variables or can be configured manually.
7
7
  *
8
- * Usage:
8
+ * AUTO-CONFIGURATION (recommended):
9
+ * Set one of these environment variables and the API will auto-configure:
10
+ * - NEXT_PUBLIC_API_URL (Next.js)
11
+ * - VITE_API_URL (Vite)
12
+ * - REACT_APP_API_URL (Create React App)
13
+ * - API_URL (generic)
14
+ *
15
+ * Then just use fetchers and hooks directly:
16
+ * ```typescript
17
+ * import { getUsers } from './_utils/fetchers'
18
+ * const users = await getUsers({ page: 1 })
19
+ * ```
20
+ *
21
+ * MANUAL CONFIGURATION:
9
22
  * ```typescript
10
- * // Configure once (e.g., in your app entry point)
11
23
  * import { configureAPI } from './api-instance'
12
24
  *
13
25
  * configureAPI({
14
26
  * baseUrl: 'https://api.example.com',
15
27
  * token: 'your-jwt-token'
16
28
  * })
17
- *
18
- * // Then use fetchers and hooks anywhere without configuration
19
- * import { getUsers } from './fetchers'
20
- * const users = await getUsers({ page: 1 })
21
29
  * ```
22
30
  *
23
31
  * For SSR or multiple instances:
24
32
  * ```typescript
25
33
  * import { API } from './index'
26
- * import { getUsers } from './fetchers'
34
+ * import { getUsers } from './_utils/fetchers'
27
35
  *
28
36
  * const api = new API('https://api.example.com')
29
37
  * const users = await getUsers({ page: 1 }, api)
@@ -33,27 +41,67 @@
33
41
  import { API, type APIOptions } from './index'
34
42
 
35
43
  let globalAPI: API | null = null
44
+ let autoConfigAttempted = false
45
+
46
+ /**
47
+ * Auto-configure from environment variable if available (Next.js pattern)
48
+ * This allows hooks and fetchers to work without explicit configureAPI() call
49
+ *
50
+ * Supported environment variables:
51
+ * - NEXT_PUBLIC_API_URL (Next.js)
52
+ * - VITE_API_URL (Vite)
53
+ * - REACT_APP_API_URL (Create React App)
54
+ * - API_URL (generic)
55
+ */
56
+ function tryAutoConfigureFromEnv(): void {
57
+ // Only attempt once
58
+ if (autoConfigAttempted) return
59
+ autoConfigAttempted = true
60
+
61
+ // Skip if already configured
62
+ if (globalAPI) return
63
+
64
+ // Skip if process is not available (pure browser without bundler)
65
+ if (typeof process === 'undefined' || !process.env) return
66
+
67
+ // Try different environment variable patterns
68
+ const baseUrl =
69
+ process.env.NEXT_PUBLIC_API_URL ||
70
+ process.env.VITE_API_URL ||
71
+ process.env.REACT_APP_API_URL ||
72
+ process.env.API_URL
73
+
74
+ if (baseUrl) {
75
+ globalAPI = new API(baseUrl)
76
+ }
77
+ }
36
78
 
37
79
  /**
38
80
  * Get the global API instance
39
- * @throws Error if API is not configured
81
+ * Auto-configures from environment variables on first call if not manually configured.
82
+ * @throws Error if API is not configured and no env variable is set
40
83
  */
41
84
  export function getAPIInstance(): API {
85
+ // Try auto-configuration on first access (lazy initialization)
86
+ tryAutoConfigureFromEnv()
87
+
42
88
  if (!globalAPI) {
43
89
  throw new Error(
44
90
  'API not configured. Call configureAPI() with your base URL before using fetchers or hooks.\n\n' +
45
91
  'Example:\n' +
46
92
  ' import { configureAPI } from "./api-instance"\n' +
47
- ' configureAPI({ baseUrl: "https://api.example.com" })'
93
+ ' configureAPI({ baseUrl: "https://api.example.com" })\n\n' +
94
+ 'Or set environment variable: NEXT_PUBLIC_API_URL, VITE_API_URL, or REACT_APP_API_URL'
48
95
  )
49
96
  }
50
97
  return globalAPI
51
98
  }
52
99
 
53
100
  /**
54
- * Check if API is configured
101
+ * Check if API is configured (or can be auto-configured)
55
102
  */
56
103
  export function isAPIConfigured(): boolean {
104
+ tryAutoConfigureFromEnv()
57
105
  return globalAPI !== null
58
106
  }
59
107
 
@@ -25,6 +25,7 @@ export class APIClient {
25
25
  private httpClient: HttpClientAdapter;
26
26
  private logger: APILogger | null = null;
27
27
  private retryConfig: RetryConfig | null = null;
28
+ private tokenGetter: (() => string | null) | null = null;
28
29
 
29
30
  // Sub-clients
30
31
  public ext_payments_payments: ExtPaymentsPayments;
@@ -35,10 +36,12 @@ export class APIClient {
35
36
  httpClient?: HttpClientAdapter;
36
37
  loggerConfig?: Partial<LoggerConfig>;
37
38
  retryConfig?: RetryConfig;
39
+ tokenGetter?: () => string | null;
38
40
  }
39
41
  ) {
40
42
  this.baseUrl = baseUrl.replace(/\/$/, '');
41
43
  this.httpClient = options?.httpClient || new FetchAdapter();
44
+ this.tokenGetter = options?.tokenGetter || null;
42
45
 
43
46
  // Initialize logger if config provided
44
47
  if (options?.loggerConfig !== undefined) {
@@ -69,6 +72,21 @@ export class APIClient {
69
72
  return null;
70
73
  }
71
74
 
75
+ /**
76
+ * Get the base URL for building streaming/download URLs.
77
+ */
78
+ getBaseUrl(): string {
79
+ return this.baseUrl;
80
+ }
81
+
82
+ /**
83
+ * Get JWT token for URL authentication (used in streaming endpoints).
84
+ * Returns null if no token getter is configured or no token is available.
85
+ */
86
+ getToken(): string | null {
87
+ return this.tokenGetter ? this.tokenGetter() : null;
88
+ }
89
+
72
90
  /**
73
91
  * Make HTTP request with Django CSRF and session handling.
74
92
  * Automatically retries on network errors and 5xx server errors.
@@ -80,6 +98,7 @@ export class APIClient {
80
98
  params?: Record<string, any>;
81
99
  body?: any;
82
100
  formData?: FormData;
101
+ binaryBody?: Blob | ArrayBuffer;
83
102
  headers?: Record<string, string>;
84
103
  }
85
104
  ): Promise<T> {
@@ -116,6 +135,7 @@ export class APIClient {
116
135
  params?: Record<string, any>;
117
136
  body?: any;
118
137
  formData?: FormData;
138
+ binaryBody?: Blob | ArrayBuffer;
119
139
  headers?: Record<string, string>;
120
140
  }
121
141
  ): Promise<T> {
@@ -129,8 +149,8 @@ export class APIClient {
129
149
  ...(options?.headers || {})
130
150
  };
131
151
 
132
- // Don't set Content-Type for FormData (browser will set it with boundary)
133
- if (!options?.formData && !headers['Content-Type']) {
152
+ // Don't set Content-Type for FormData/binaryBody (browser will set it with boundary)
153
+ if (!options?.formData && !options?.binaryBody && !headers['Content-Type']) {
134
154
  headers['Content-Type'] = 'application/json';
135
155
  }
136
156
 
@@ -157,6 +177,7 @@ export class APIClient {
157
177
  params: options?.params,
158
178
  body: options?.body,
159
179
  formData: options?.formData,
180
+ binaryBody: options?.binaryBody,
160
181
  });
161
182
 
162
183
  const duration = Date.now() - startTime;