@djangocfg/ext-support 1.0.9 → 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/config.cjs CHANGED
@@ -27,7 +27,7 @@ var import_ext_base = require("@djangocfg/ext-base");
27
27
  // package.json
28
28
  var package_default = {
29
29
  name: "@djangocfg/ext-support",
30
- version: "1.0.9",
30
+ version: "1.0.10",
31
31
  description: "Support ticket system extension for DjangoCFG",
32
32
  keywords: [
33
33
  "django",
package/dist/config.js CHANGED
@@ -4,7 +4,7 @@ import { createExtensionConfig } from "@djangocfg/ext-base";
4
4
  // package.json
5
5
  var package_default = {
6
6
  name: "@djangocfg/ext-support",
7
- version: "1.0.9",
7
+ version: "1.0.10",
8
8
  description: "Support ticket system extension for DjangoCFG",
9
9
  keywords: [
10
10
  "django",
package/dist/hooks.cjs CHANGED
@@ -163,7 +163,7 @@ var models_exports = {};
163
163
  // src/api/generated/ext_support/http.ts
164
164
  var FetchAdapter = class {
165
165
  async request(request) {
166
- const { method, url, headers, body, params, formData } = request;
166
+ const { method, url, headers, body, params, formData, binaryBody } = request;
167
167
  let finalUrl = url;
168
168
  if (params) {
169
169
  const searchParams = new URLSearchParams();
@@ -181,6 +181,9 @@ var FetchAdapter = class {
181
181
  let requestBody;
182
182
  if (formData) {
183
183
  requestBody = formData;
184
+ } else if (binaryBody) {
185
+ finalHeaders["Content-Type"] = "application/octet-stream";
186
+ requestBody = binaryBody;
184
187
  } else if (body) {
185
188
  finalHeaders["Content-Type"] = "application/json";
186
189
  requestBody = JSON.stringify(body);
@@ -505,11 +508,13 @@ var APIClient = class {
505
508
  httpClient;
506
509
  logger = null;
507
510
  retryConfig = null;
511
+ tokenGetter = null;
508
512
  // Sub-clients
509
513
  ext_support_support;
510
514
  constructor(baseUrl, options) {
511
515
  this.baseUrl = baseUrl.replace(/\/$/, "");
512
516
  this.httpClient = options?.httpClient || new FetchAdapter();
517
+ this.tokenGetter = options?.tokenGetter || null;
513
518
  if (options?.loggerConfig !== void 0) {
514
519
  this.logger = new APILogger(options.loggerConfig);
515
520
  }
@@ -532,6 +537,19 @@ var APIClient = class {
532
537
  }
533
538
  return null;
534
539
  }
540
+ /**
541
+ * Get the base URL for building streaming/download URLs.
542
+ */
543
+ getBaseUrl() {
544
+ return this.baseUrl;
545
+ }
546
+ /**
547
+ * Get JWT token for URL authentication (used in streaming endpoints).
548
+ * Returns null if no token getter is configured or no token is available.
549
+ */
550
+ getToken() {
551
+ return this.tokenGetter ? this.tokenGetter() : null;
552
+ }
535
553
  /**
536
554
  * Make HTTP request with Django CSRF and session handling.
537
555
  * Automatically retries on network errors and 5xx server errors.
@@ -562,7 +580,7 @@ var APIClient = class {
562
580
  const headers = {
563
581
  ...options?.headers || {}
564
582
  };
565
- if (!options?.formData && !headers["Content-Type"]) {
583
+ if (!options?.formData && !options?.binaryBody && !headers["Content-Type"]) {
566
584
  headers["Content-Type"] = "application/json";
567
585
  }
568
586
  if (this.logger) {
@@ -581,7 +599,8 @@ var APIClient = class {
581
599
  headers,
582
600
  params: options?.params,
583
601
  body: options?.body,
584
- formData: options?.formData
602
+ formData: options?.formData,
603
+ binaryBody: options?.binaryBody
585
604
  });
586
605
  const duration = Date.now() - startTime;
587
606
  if (response.status >= 400) {
@@ -995,15 +1014,28 @@ __export(fetchers_exports, {
995
1014
 
996
1015
  // src/api/generated/ext_support/api-instance.ts
997
1016
  var globalAPI = null;
1017
+ var autoConfigAttempted = false;
1018
+ function tryAutoConfigureFromEnv() {
1019
+ if (autoConfigAttempted) return;
1020
+ autoConfigAttempted = true;
1021
+ if (globalAPI) return;
1022
+ if (typeof process === "undefined" || !process.env) return;
1023
+ const baseUrl = process.env.NEXT_PUBLIC_API_URL || process.env.VITE_API_URL || process.env.REACT_APP_API_URL || process.env.API_URL;
1024
+ if (baseUrl) {
1025
+ globalAPI = new API(baseUrl);
1026
+ }
1027
+ }
998
1028
  function getAPIInstance() {
1029
+ tryAutoConfigureFromEnv();
999
1030
  if (!globalAPI) {
1000
1031
  throw new Error(
1001
- '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" })'
1032
+ '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'
1002
1033
  );
1003
1034
  }
1004
1035
  return globalAPI;
1005
1036
  }
1006
1037
  function isAPIConfigured() {
1038
+ tryAutoConfigureFromEnv();
1007
1039
  return globalAPI !== null;
1008
1040
  }
1009
1041
  function configureAPI(config) {
@@ -1486,7 +1518,8 @@ var API = class {
1486
1518
  this._loadTokensFromStorage();
1487
1519
  this._client = new APIClient(this.baseUrl, {
1488
1520
  retryConfig: this.options?.retryConfig,
1489
- loggerConfig: this.options?.loggerConfig
1521
+ loggerConfig: this.options?.loggerConfig,
1522
+ tokenGetter: () => this.getToken()
1490
1523
  });
1491
1524
  this._injectAuthHeader();
1492
1525
  this.ext_support_support = this._client.ext_support_support;
@@ -1498,7 +1531,8 @@ var API = class {
1498
1531
  _reinitClients() {
1499
1532
  this._client = new APIClient(this.baseUrl, {
1500
1533
  retryConfig: this.options?.retryConfig,
1501
- loggerConfig: this.options?.loggerConfig
1534
+ loggerConfig: this.options?.loggerConfig,
1535
+ tokenGetter: () => this.getToken()
1502
1536
  });
1503
1537
  this._injectAuthHeader();
1504
1538
  this.ext_support_support = this._client.ext_support_support;
@@ -1589,6 +1623,7 @@ var API = class {
1589
1623
  return "./schema.json";
1590
1624
  }
1591
1625
  };
1626
+ api.initializeExtensionAPI(configureAPI);
1592
1627
  var apiSupport = api.createExtensionAPI(API);
1593
1628
  function useSupportTicketsList(params, client) {
1594
1629
  return useSWR__default.default(
@@ -2469,17 +2504,10 @@ var CreateTicketDialog = () => {
2469
2504
  try {
2470
2505
  await createTicket(data);
2471
2506
  form.reset();
2472
- toast({
2473
- title: "Success",
2474
- description: "Support ticket created successfully"
2475
- });
2507
+ toast.success("Support ticket created successfully");
2476
2508
  } catch (error) {
2477
2509
  supportLogger.error("Failed to create ticket:", error);
2478
- toast({
2479
- title: "Error",
2480
- description: "Failed to create ticket. Please try again.",
2481
- variant: "destructive"
2482
- });
2510
+ toast.error("Failed to create ticket. Please try again.");
2483
2511
  } finally {
2484
2512
  setIsSubmitting(false);
2485
2513
  }
@@ -2628,7 +2656,7 @@ var SupportLayout = () => {
2628
2656
  // package.json
2629
2657
  var package_default = {
2630
2658
  name: "@djangocfg/ext-support",
2631
- version: "1.0.9",
2659
+ version: "1.0.10",
2632
2660
  description: "Support ticket system extension for DjangoCFG",
2633
2661
  keywords: [
2634
2662
  "django",
package/dist/hooks.js CHANGED
@@ -1,7 +1,7 @@
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 { Clock, MessageSquare, Loader2, Send, Plus, Headphones, User, ArrowLeft, LifeBuoy } from 'lucide-react';
6
6
  import React7, { createContext, useContext, useState, useCallback, useEffect, useRef } from 'react';
7
7
  import { Card, CardContent, Badge, Skeleton, ScrollArea, Button, Textarea, useToast as useToast$1, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Form, FormField, FormItem, FormLabel, FormControl, Input, FormMessage, Avatar, AvatarImage, AvatarFallback, ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@djangocfg/ui-core';
@@ -153,7 +153,7 @@ var models_exports = {};
153
153
  // src/api/generated/ext_support/http.ts
154
154
  var FetchAdapter = class {
155
155
  async request(request) {
156
- const { method, url, headers, body, params, formData } = request;
156
+ const { method, url, headers, body, params, formData, binaryBody } = request;
157
157
  let finalUrl = url;
158
158
  if (params) {
159
159
  const searchParams = new URLSearchParams();
@@ -171,6 +171,9 @@ var FetchAdapter = class {
171
171
  let requestBody;
172
172
  if (formData) {
173
173
  requestBody = formData;
174
+ } else if (binaryBody) {
175
+ finalHeaders["Content-Type"] = "application/octet-stream";
176
+ requestBody = binaryBody;
174
177
  } else if (body) {
175
178
  finalHeaders["Content-Type"] = "application/json";
176
179
  requestBody = JSON.stringify(body);
@@ -495,11 +498,13 @@ var APIClient = class {
495
498
  httpClient;
496
499
  logger = null;
497
500
  retryConfig = null;
501
+ tokenGetter = null;
498
502
  // Sub-clients
499
503
  ext_support_support;
500
504
  constructor(baseUrl, options) {
501
505
  this.baseUrl = baseUrl.replace(/\/$/, "");
502
506
  this.httpClient = options?.httpClient || new FetchAdapter();
507
+ this.tokenGetter = options?.tokenGetter || null;
503
508
  if (options?.loggerConfig !== void 0) {
504
509
  this.logger = new APILogger(options.loggerConfig);
505
510
  }
@@ -522,6 +527,19 @@ var APIClient = class {
522
527
  }
523
528
  return null;
524
529
  }
530
+ /**
531
+ * Get the base URL for building streaming/download URLs.
532
+ */
533
+ getBaseUrl() {
534
+ return this.baseUrl;
535
+ }
536
+ /**
537
+ * Get JWT token for URL authentication (used in streaming endpoints).
538
+ * Returns null if no token getter is configured or no token is available.
539
+ */
540
+ getToken() {
541
+ return this.tokenGetter ? this.tokenGetter() : null;
542
+ }
525
543
  /**
526
544
  * Make HTTP request with Django CSRF and session handling.
527
545
  * Automatically retries on network errors and 5xx server errors.
@@ -552,7 +570,7 @@ var APIClient = class {
552
570
  const headers = {
553
571
  ...options?.headers || {}
554
572
  };
555
- if (!options?.formData && !headers["Content-Type"]) {
573
+ if (!options?.formData && !options?.binaryBody && !headers["Content-Type"]) {
556
574
  headers["Content-Type"] = "application/json";
557
575
  }
558
576
  if (this.logger) {
@@ -571,7 +589,8 @@ var APIClient = class {
571
589
  headers,
572
590
  params: options?.params,
573
591
  body: options?.body,
574
- formData: options?.formData
592
+ formData: options?.formData,
593
+ binaryBody: options?.binaryBody
575
594
  });
576
595
  const duration = Date.now() - startTime;
577
596
  if (response.status >= 400) {
@@ -985,15 +1004,28 @@ __export(fetchers_exports, {
985
1004
 
986
1005
  // src/api/generated/ext_support/api-instance.ts
987
1006
  var globalAPI = null;
1007
+ var autoConfigAttempted = false;
1008
+ function tryAutoConfigureFromEnv() {
1009
+ if (autoConfigAttempted) return;
1010
+ autoConfigAttempted = true;
1011
+ if (globalAPI) return;
1012
+ if (typeof process === "undefined" || !process.env) return;
1013
+ const baseUrl = process.env.NEXT_PUBLIC_API_URL || process.env.VITE_API_URL || process.env.REACT_APP_API_URL || process.env.API_URL;
1014
+ if (baseUrl) {
1015
+ globalAPI = new API(baseUrl);
1016
+ }
1017
+ }
988
1018
  function getAPIInstance() {
1019
+ tryAutoConfigureFromEnv();
989
1020
  if (!globalAPI) {
990
1021
  throw new Error(
991
- '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" })'
1022
+ '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'
992
1023
  );
993
1024
  }
994
1025
  return globalAPI;
995
1026
  }
996
1027
  function isAPIConfigured() {
1028
+ tryAutoConfigureFromEnv();
997
1029
  return globalAPI !== null;
998
1030
  }
999
1031
  function configureAPI(config) {
@@ -1476,7 +1508,8 @@ var API = class {
1476
1508
  this._loadTokensFromStorage();
1477
1509
  this._client = new APIClient(this.baseUrl, {
1478
1510
  retryConfig: this.options?.retryConfig,
1479
- loggerConfig: this.options?.loggerConfig
1511
+ loggerConfig: this.options?.loggerConfig,
1512
+ tokenGetter: () => this.getToken()
1480
1513
  });
1481
1514
  this._injectAuthHeader();
1482
1515
  this.ext_support_support = this._client.ext_support_support;
@@ -1488,7 +1521,8 @@ var API = class {
1488
1521
  _reinitClients() {
1489
1522
  this._client = new APIClient(this.baseUrl, {
1490
1523
  retryConfig: this.options?.retryConfig,
1491
- loggerConfig: this.options?.loggerConfig
1524
+ loggerConfig: this.options?.loggerConfig,
1525
+ tokenGetter: () => this.getToken()
1492
1526
  });
1493
1527
  this._injectAuthHeader();
1494
1528
  this.ext_support_support = this._client.ext_support_support;
@@ -1579,6 +1613,7 @@ var API = class {
1579
1613
  return "./schema.json";
1580
1614
  }
1581
1615
  };
1616
+ initializeExtensionAPI(configureAPI);
1582
1617
  var apiSupport = createExtensionAPI(API);
1583
1618
  function useSupportTicketsList(params, client) {
1584
1619
  return useSWR(
@@ -2459,17 +2494,10 @@ var CreateTicketDialog = () => {
2459
2494
  try {
2460
2495
  await createTicket(data);
2461
2496
  form.reset();
2462
- toast({
2463
- title: "Success",
2464
- description: "Support ticket created successfully"
2465
- });
2497
+ toast.success("Support ticket created successfully");
2466
2498
  } catch (error) {
2467
2499
  supportLogger.error("Failed to create ticket:", error);
2468
- toast({
2469
- title: "Error",
2470
- description: "Failed to create ticket. Please try again.",
2471
- variant: "destructive"
2472
- });
2500
+ toast.error("Failed to create ticket. Please try again.");
2473
2501
  } finally {
2474
2502
  setIsSubmitting(false);
2475
2503
  }
@@ -2618,7 +2646,7 @@ var SupportLayout = () => {
2618
2646
  // package.json
2619
2647
  var package_default = {
2620
2648
  name: "@djangocfg/ext-support",
2621
- version: "1.0.9",
2649
+ version: "1.0.10",
2622
2650
  description: "Support ticket system extension for DjangoCFG",
2623
2651
  keywords: [
2624
2652
  "django",
package/dist/index.cjs CHANGED
@@ -163,7 +163,7 @@ var models_exports = {};
163
163
  // src/api/generated/ext_support/http.ts
164
164
  var FetchAdapter = class {
165
165
  async request(request) {
166
- const { method, url, headers, body, params, formData } = request;
166
+ const { method, url, headers, body, params, formData, binaryBody } = request;
167
167
  let finalUrl = url;
168
168
  if (params) {
169
169
  const searchParams = new URLSearchParams();
@@ -181,6 +181,9 @@ var FetchAdapter = class {
181
181
  let requestBody;
182
182
  if (formData) {
183
183
  requestBody = formData;
184
+ } else if (binaryBody) {
185
+ finalHeaders["Content-Type"] = "application/octet-stream";
186
+ requestBody = binaryBody;
184
187
  } else if (body) {
185
188
  finalHeaders["Content-Type"] = "application/json";
186
189
  requestBody = JSON.stringify(body);
@@ -505,11 +508,13 @@ var APIClient = class {
505
508
  httpClient;
506
509
  logger = null;
507
510
  retryConfig = null;
511
+ tokenGetter = null;
508
512
  // Sub-clients
509
513
  ext_support_support;
510
514
  constructor(baseUrl, options) {
511
515
  this.baseUrl = baseUrl.replace(/\/$/, "");
512
516
  this.httpClient = options?.httpClient || new FetchAdapter();
517
+ this.tokenGetter = options?.tokenGetter || null;
513
518
  if (options?.loggerConfig !== void 0) {
514
519
  this.logger = new APILogger(options.loggerConfig);
515
520
  }
@@ -532,6 +537,19 @@ var APIClient = class {
532
537
  }
533
538
  return null;
534
539
  }
540
+ /**
541
+ * Get the base URL for building streaming/download URLs.
542
+ */
543
+ getBaseUrl() {
544
+ return this.baseUrl;
545
+ }
546
+ /**
547
+ * Get JWT token for URL authentication (used in streaming endpoints).
548
+ * Returns null if no token getter is configured or no token is available.
549
+ */
550
+ getToken() {
551
+ return this.tokenGetter ? this.tokenGetter() : null;
552
+ }
535
553
  /**
536
554
  * Make HTTP request with Django CSRF and session handling.
537
555
  * Automatically retries on network errors and 5xx server errors.
@@ -562,7 +580,7 @@ var APIClient = class {
562
580
  const headers = {
563
581
  ...options?.headers || {}
564
582
  };
565
- if (!options?.formData && !headers["Content-Type"]) {
583
+ if (!options?.formData && !options?.binaryBody && !headers["Content-Type"]) {
566
584
  headers["Content-Type"] = "application/json";
567
585
  }
568
586
  if (this.logger) {
@@ -581,7 +599,8 @@ var APIClient = class {
581
599
  headers,
582
600
  params: options?.params,
583
601
  body: options?.body,
584
- formData: options?.formData
602
+ formData: options?.formData,
603
+ binaryBody: options?.binaryBody
585
604
  });
586
605
  const duration = Date.now() - startTime;
587
606
  if (response.status >= 400) {
@@ -995,15 +1014,28 @@ __export(fetchers_exports, {
995
1014
 
996
1015
  // src/api/generated/ext_support/api-instance.ts
997
1016
  var globalAPI = null;
1017
+ var autoConfigAttempted = false;
1018
+ function tryAutoConfigureFromEnv() {
1019
+ if (autoConfigAttempted) return;
1020
+ autoConfigAttempted = true;
1021
+ if (globalAPI) return;
1022
+ if (typeof process === "undefined" || !process.env) return;
1023
+ const baseUrl = process.env.NEXT_PUBLIC_API_URL || process.env.VITE_API_URL || process.env.REACT_APP_API_URL || process.env.API_URL;
1024
+ if (baseUrl) {
1025
+ globalAPI = new API(baseUrl);
1026
+ }
1027
+ }
998
1028
  function getAPIInstance() {
1029
+ tryAutoConfigureFromEnv();
999
1030
  if (!globalAPI) {
1000
1031
  throw new Error(
1001
- '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" })'
1032
+ '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'
1002
1033
  );
1003
1034
  }
1004
1035
  return globalAPI;
1005
1036
  }
1006
1037
  function isAPIConfigured() {
1038
+ tryAutoConfigureFromEnv();
1007
1039
  return globalAPI !== null;
1008
1040
  }
1009
1041
  function configureAPI(config) {
@@ -1486,7 +1518,8 @@ var API = class {
1486
1518
  this._loadTokensFromStorage();
1487
1519
  this._client = new APIClient(this.baseUrl, {
1488
1520
  retryConfig: this.options?.retryConfig,
1489
- loggerConfig: this.options?.loggerConfig
1521
+ loggerConfig: this.options?.loggerConfig,
1522
+ tokenGetter: () => this.getToken()
1490
1523
  });
1491
1524
  this._injectAuthHeader();
1492
1525
  this.ext_support_support = this._client.ext_support_support;
@@ -1498,7 +1531,8 @@ var API = class {
1498
1531
  _reinitClients() {
1499
1532
  this._client = new APIClient(this.baseUrl, {
1500
1533
  retryConfig: this.options?.retryConfig,
1501
- loggerConfig: this.options?.loggerConfig
1534
+ loggerConfig: this.options?.loggerConfig,
1535
+ tokenGetter: () => this.getToken()
1502
1536
  });
1503
1537
  this._injectAuthHeader();
1504
1538
  this.ext_support_support = this._client.ext_support_support;
@@ -1589,6 +1623,7 @@ var API = class {
1589
1623
  return "./schema.json";
1590
1624
  }
1591
1625
  };
1626
+ api.initializeExtensionAPI(configureAPI);
1592
1627
  var apiSupport = api.createExtensionAPI(API);
1593
1628
  function useSupportTicketsList(params, client) {
1594
1629
  return useSWR__default.default(
@@ -2469,17 +2504,10 @@ var CreateTicketDialog = () => {
2469
2504
  try {
2470
2505
  await createTicket(data);
2471
2506
  form.reset();
2472
- toast({
2473
- title: "Success",
2474
- description: "Support ticket created successfully"
2475
- });
2507
+ toast.success("Support ticket created successfully");
2476
2508
  } catch (error) {
2477
2509
  supportLogger.error("Failed to create ticket:", error);
2478
- toast({
2479
- title: "Error",
2480
- description: "Failed to create ticket. Please try again.",
2481
- variant: "destructive"
2482
- });
2510
+ toast.error("Failed to create ticket. Please try again.");
2483
2511
  } finally {
2484
2512
  setIsSubmitting(false);
2485
2513
  }
@@ -2628,7 +2656,7 @@ var SupportLayout = () => {
2628
2656
  // package.json
2629
2657
  var package_default = {
2630
2658
  name: "@djangocfg/ext-support",
2631
- version: "1.0.9",
2659
+ version: "1.0.10",
2632
2660
  description: "Support ticket system extension for DjangoCFG",
2633
2661
  keywords: [
2634
2662
  "django",
package/dist/index.d.cts CHANGED
@@ -300,6 +300,8 @@ interface HttpRequest {
300
300
  params?: Record<string, any>;
301
301
  /** FormData for file uploads (multipart/form-data) */
302
302
  formData?: FormData;
303
+ /** Binary data for octet-stream uploads */
304
+ binaryBody?: Blob | ArrayBuffer;
303
305
  }
304
306
  interface HttpResponse<T = any> {
305
307
  data: T;
@@ -552,11 +554,13 @@ declare class APIClient {
552
554
  private httpClient;
553
555
  private logger;
554
556
  private retryConfig;
557
+ private tokenGetter;
555
558
  ext_support_support: ExtSupportSupport;
556
559
  constructor(baseUrl: string, options?: {
557
560
  httpClient?: HttpClientAdapter;
558
561
  loggerConfig?: Partial<LoggerConfig>;
559
562
  retryConfig?: RetryConfig;
563
+ tokenGetter?: () => string | null;
560
564
  });
561
565
  /**
562
566
  * Get CSRF token from cookies (for SessionAuthentication).
@@ -564,6 +568,15 @@ declare class APIClient {
564
568
  * Returns null if cookie doesn't exist (JWT-only auth).
565
569
  */
566
570
  getCsrfToken(): string | null;
571
+ /**
572
+ * Get the base URL for building streaming/download URLs.
573
+ */
574
+ getBaseUrl(): string;
575
+ /**
576
+ * Get JWT token for URL authentication (used in streaming endpoints).
577
+ * Returns null if no token getter is configured or no token is available.
578
+ */
579
+ getToken(): string | null;
567
580
  /**
568
581
  * Make HTTP request with Django CSRF and session handling.
569
582
  * Automatically retries on network errors and 5xx server errors.
@@ -572,6 +585,7 @@ declare class APIClient {
572
585
  params?: Record<string, any>;
573
586
  body?: any;
574
587
  formData?: FormData;
588
+ binaryBody?: Blob | ArrayBuffer;
575
589
  headers?: Record<string, string>;
576
590
  }): Promise<T>;
577
591
  /**
@@ -1102,30 +1116,38 @@ declare namespace index {
1102
1116
  }
1103
1117
 
1104
1118
  /**
1105
- * Global API Instance - Singleton configuration
1119
+ * Global API Instance - Singleton configuration with auto-configuration support
1106
1120
  *
1107
- * This module provides a global API instance that can be configured once
1108
- * and used throughout your application.
1121
+ * This module provides a global API instance that auto-configures from
1122
+ * environment variables or can be configured manually.
1109
1123
  *
1110
- * Usage:
1124
+ * AUTO-CONFIGURATION (recommended):
1125
+ * Set one of these environment variables and the API will auto-configure:
1126
+ * - NEXT_PUBLIC_API_URL (Next.js)
1127
+ * - VITE_API_URL (Vite)
1128
+ * - REACT_APP_API_URL (Create React App)
1129
+ * - API_URL (generic)
1130
+ *
1131
+ * Then just use fetchers and hooks directly:
1132
+ * ```typescript
1133
+ * import { getUsers } from './_utils/fetchers'
1134
+ * const users = await getUsers({ page: 1 })
1135
+ * ```
1136
+ *
1137
+ * MANUAL CONFIGURATION:
1111
1138
  * ```typescript
1112
- * // Configure once (e.g., in your app entry point)
1113
1139
  * import { configureAPI } from './api-instance'
1114
1140
  *
1115
1141
  * configureAPI({
1116
1142
  * baseUrl: 'https://api.example.com',
1117
1143
  * token: 'your-jwt-token'
1118
1144
  * })
1119
- *
1120
- * // Then use fetchers and hooks anywhere without configuration
1121
- * import { getUsers } from './fetchers'
1122
- * const users = await getUsers({ page: 1 })
1123
1145
  * ```
1124
1146
  *
1125
1147
  * For SSR or multiple instances:
1126
1148
  * ```typescript
1127
1149
  * import { API } from './index'
1128
- * import { getUsers } from './fetchers'
1150
+ * import { getUsers } from './_utils/fetchers'
1129
1151
  *
1130
1152
  * const api = new API('https://api.example.com')
1131
1153
  * const users = await getUsers({ page: 1 }, api)
@@ -1134,11 +1156,12 @@ declare namespace index {
1134
1156
 
1135
1157
  /**
1136
1158
  * Get the global API instance
1137
- * @throws Error if API is not configured
1159
+ * Auto-configures from environment variables on first call if not manually configured.
1160
+ * @throws Error if API is not configured and no env variable is set
1138
1161
  */
1139
1162
  declare function getAPIInstance(): API;
1140
1163
  /**
1141
- * Check if API is configured
1164
+ * Check if API is configured (or can be auto-configured)
1142
1165
  */
1143
1166
  declare function isAPIConfigured(): boolean;
1144
1167
  /**
package/dist/index.d.ts CHANGED
@@ -300,6 +300,8 @@ interface HttpRequest {
300
300
  params?: Record<string, any>;
301
301
  /** FormData for file uploads (multipart/form-data) */
302
302
  formData?: FormData;
303
+ /** Binary data for octet-stream uploads */
304
+ binaryBody?: Blob | ArrayBuffer;
303
305
  }
304
306
  interface HttpResponse<T = any> {
305
307
  data: T;
@@ -552,11 +554,13 @@ declare class APIClient {
552
554
  private httpClient;
553
555
  private logger;
554
556
  private retryConfig;
557
+ private tokenGetter;
555
558
  ext_support_support: ExtSupportSupport;
556
559
  constructor(baseUrl: string, options?: {
557
560
  httpClient?: HttpClientAdapter;
558
561
  loggerConfig?: Partial<LoggerConfig>;
559
562
  retryConfig?: RetryConfig;
563
+ tokenGetter?: () => string | null;
560
564
  });
561
565
  /**
562
566
  * Get CSRF token from cookies (for SessionAuthentication).
@@ -564,6 +568,15 @@ declare class APIClient {
564
568
  * Returns null if cookie doesn't exist (JWT-only auth).
565
569
  */
566
570
  getCsrfToken(): string | null;
571
+ /**
572
+ * Get the base URL for building streaming/download URLs.
573
+ */
574
+ getBaseUrl(): string;
575
+ /**
576
+ * Get JWT token for URL authentication (used in streaming endpoints).
577
+ * Returns null if no token getter is configured or no token is available.
578
+ */
579
+ getToken(): string | null;
567
580
  /**
568
581
  * Make HTTP request with Django CSRF and session handling.
569
582
  * Automatically retries on network errors and 5xx server errors.
@@ -572,6 +585,7 @@ declare class APIClient {
572
585
  params?: Record<string, any>;
573
586
  body?: any;
574
587
  formData?: FormData;
588
+ binaryBody?: Blob | ArrayBuffer;
575
589
  headers?: Record<string, string>;
576
590
  }): Promise<T>;
577
591
  /**
@@ -1102,30 +1116,38 @@ declare namespace index {
1102
1116
  }
1103
1117
 
1104
1118
  /**
1105
- * Global API Instance - Singleton configuration
1119
+ * Global API Instance - Singleton configuration with auto-configuration support
1106
1120
  *
1107
- * This module provides a global API instance that can be configured once
1108
- * and used throughout your application.
1121
+ * This module provides a global API instance that auto-configures from
1122
+ * environment variables or can be configured manually.
1109
1123
  *
1110
- * Usage:
1124
+ * AUTO-CONFIGURATION (recommended):
1125
+ * Set one of these environment variables and the API will auto-configure:
1126
+ * - NEXT_PUBLIC_API_URL (Next.js)
1127
+ * - VITE_API_URL (Vite)
1128
+ * - REACT_APP_API_URL (Create React App)
1129
+ * - API_URL (generic)
1130
+ *
1131
+ * Then just use fetchers and hooks directly:
1132
+ * ```typescript
1133
+ * import { getUsers } from './_utils/fetchers'
1134
+ * const users = await getUsers({ page: 1 })
1135
+ * ```
1136
+ *
1137
+ * MANUAL CONFIGURATION:
1111
1138
  * ```typescript
1112
- * // Configure once (e.g., in your app entry point)
1113
1139
  * import { configureAPI } from './api-instance'
1114
1140
  *
1115
1141
  * configureAPI({
1116
1142
  * baseUrl: 'https://api.example.com',
1117
1143
  * token: 'your-jwt-token'
1118
1144
  * })
1119
- *
1120
- * // Then use fetchers and hooks anywhere without configuration
1121
- * import { getUsers } from './fetchers'
1122
- * const users = await getUsers({ page: 1 })
1123
1145
  * ```
1124
1146
  *
1125
1147
  * For SSR or multiple instances:
1126
1148
  * ```typescript
1127
1149
  * import { API } from './index'
1128
- * import { getUsers } from './fetchers'
1150
+ * import { getUsers } from './_utils/fetchers'
1129
1151
  *
1130
1152
  * const api = new API('https://api.example.com')
1131
1153
  * const users = await getUsers({ page: 1 }, api)
@@ -1134,11 +1156,12 @@ declare namespace index {
1134
1156
 
1135
1157
  /**
1136
1158
  * Get the global API instance
1137
- * @throws Error if API is not configured
1159
+ * Auto-configures from environment variables on first call if not manually configured.
1160
+ * @throws Error if API is not configured and no env variable is set
1138
1161
  */
1139
1162
  declare function getAPIInstance(): API;
1140
1163
  /**
1141
- * Check if API is configured
1164
+ * Check if API is configured (or can be auto-configured)
1142
1165
  */
1143
1166
  declare function isAPIConfigured(): boolean;
1144
1167
  /**
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
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 { ArrowLeft, LifeBuoy, Plus, MessageSquare, Loader2, Send, Headphones, User, Clock } from 'lucide-react';
6
6
  import React7, { createContext, useState, useCallback, useEffect, useContext, useRef } from 'react';
7
7
  import { Button, ResizablePanelGroup, ResizablePanel, ResizableHandle, Skeleton, ScrollArea, Textarea, useToast as useToast$1, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Form, FormField, FormItem, FormLabel, FormControl, Input, FormMessage, Avatar, AvatarImage, AvatarFallback, Card, CardContent, Badge } from '@djangocfg/ui-core';
@@ -153,7 +153,7 @@ var models_exports = {};
153
153
  // src/api/generated/ext_support/http.ts
154
154
  var FetchAdapter = class {
155
155
  async request(request) {
156
- const { method, url, headers, body, params, formData } = request;
156
+ const { method, url, headers, body, params, formData, binaryBody } = request;
157
157
  let finalUrl = url;
158
158
  if (params) {
159
159
  const searchParams = new URLSearchParams();
@@ -171,6 +171,9 @@ var FetchAdapter = class {
171
171
  let requestBody;
172
172
  if (formData) {
173
173
  requestBody = formData;
174
+ } else if (binaryBody) {
175
+ finalHeaders["Content-Type"] = "application/octet-stream";
176
+ requestBody = binaryBody;
174
177
  } else if (body) {
175
178
  finalHeaders["Content-Type"] = "application/json";
176
179
  requestBody = JSON.stringify(body);
@@ -495,11 +498,13 @@ var APIClient = class {
495
498
  httpClient;
496
499
  logger = null;
497
500
  retryConfig = null;
501
+ tokenGetter = null;
498
502
  // Sub-clients
499
503
  ext_support_support;
500
504
  constructor(baseUrl, options) {
501
505
  this.baseUrl = baseUrl.replace(/\/$/, "");
502
506
  this.httpClient = options?.httpClient || new FetchAdapter();
507
+ this.tokenGetter = options?.tokenGetter || null;
503
508
  if (options?.loggerConfig !== void 0) {
504
509
  this.logger = new APILogger(options.loggerConfig);
505
510
  }
@@ -522,6 +527,19 @@ var APIClient = class {
522
527
  }
523
528
  return null;
524
529
  }
530
+ /**
531
+ * Get the base URL for building streaming/download URLs.
532
+ */
533
+ getBaseUrl() {
534
+ return this.baseUrl;
535
+ }
536
+ /**
537
+ * Get JWT token for URL authentication (used in streaming endpoints).
538
+ * Returns null if no token getter is configured or no token is available.
539
+ */
540
+ getToken() {
541
+ return this.tokenGetter ? this.tokenGetter() : null;
542
+ }
525
543
  /**
526
544
  * Make HTTP request with Django CSRF and session handling.
527
545
  * Automatically retries on network errors and 5xx server errors.
@@ -552,7 +570,7 @@ var APIClient = class {
552
570
  const headers = {
553
571
  ...options?.headers || {}
554
572
  };
555
- if (!options?.formData && !headers["Content-Type"]) {
573
+ if (!options?.formData && !options?.binaryBody && !headers["Content-Type"]) {
556
574
  headers["Content-Type"] = "application/json";
557
575
  }
558
576
  if (this.logger) {
@@ -571,7 +589,8 @@ var APIClient = class {
571
589
  headers,
572
590
  params: options?.params,
573
591
  body: options?.body,
574
- formData: options?.formData
592
+ formData: options?.formData,
593
+ binaryBody: options?.binaryBody
575
594
  });
576
595
  const duration = Date.now() - startTime;
577
596
  if (response.status >= 400) {
@@ -985,15 +1004,28 @@ __export(fetchers_exports, {
985
1004
 
986
1005
  // src/api/generated/ext_support/api-instance.ts
987
1006
  var globalAPI = null;
1007
+ var autoConfigAttempted = false;
1008
+ function tryAutoConfigureFromEnv() {
1009
+ if (autoConfigAttempted) return;
1010
+ autoConfigAttempted = true;
1011
+ if (globalAPI) return;
1012
+ if (typeof process === "undefined" || !process.env) return;
1013
+ const baseUrl = process.env.NEXT_PUBLIC_API_URL || process.env.VITE_API_URL || process.env.REACT_APP_API_URL || process.env.API_URL;
1014
+ if (baseUrl) {
1015
+ globalAPI = new API(baseUrl);
1016
+ }
1017
+ }
988
1018
  function getAPIInstance() {
1019
+ tryAutoConfigureFromEnv();
989
1020
  if (!globalAPI) {
990
1021
  throw new Error(
991
- '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" })'
1022
+ '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'
992
1023
  );
993
1024
  }
994
1025
  return globalAPI;
995
1026
  }
996
1027
  function isAPIConfigured() {
1028
+ tryAutoConfigureFromEnv();
997
1029
  return globalAPI !== null;
998
1030
  }
999
1031
  function configureAPI(config) {
@@ -1476,7 +1508,8 @@ var API = class {
1476
1508
  this._loadTokensFromStorage();
1477
1509
  this._client = new APIClient(this.baseUrl, {
1478
1510
  retryConfig: this.options?.retryConfig,
1479
- loggerConfig: this.options?.loggerConfig
1511
+ loggerConfig: this.options?.loggerConfig,
1512
+ tokenGetter: () => this.getToken()
1480
1513
  });
1481
1514
  this._injectAuthHeader();
1482
1515
  this.ext_support_support = this._client.ext_support_support;
@@ -1488,7 +1521,8 @@ var API = class {
1488
1521
  _reinitClients() {
1489
1522
  this._client = new APIClient(this.baseUrl, {
1490
1523
  retryConfig: this.options?.retryConfig,
1491
- loggerConfig: this.options?.loggerConfig
1524
+ loggerConfig: this.options?.loggerConfig,
1525
+ tokenGetter: () => this.getToken()
1492
1526
  });
1493
1527
  this._injectAuthHeader();
1494
1528
  this.ext_support_support = this._client.ext_support_support;
@@ -1579,6 +1613,7 @@ var API = class {
1579
1613
  return "./schema.json";
1580
1614
  }
1581
1615
  };
1616
+ initializeExtensionAPI(configureAPI);
1582
1617
  var apiSupport = createExtensionAPI(API);
1583
1618
  function useSupportTicketsList(params, client) {
1584
1619
  return useSWR(
@@ -2459,17 +2494,10 @@ var CreateTicketDialog = () => {
2459
2494
  try {
2460
2495
  await createTicket(data);
2461
2496
  form.reset();
2462
- toast({
2463
- title: "Success",
2464
- description: "Support ticket created successfully"
2465
- });
2497
+ toast.success("Support ticket created successfully");
2466
2498
  } catch (error) {
2467
2499
  supportLogger.error("Failed to create ticket:", error);
2468
- toast({
2469
- title: "Error",
2470
- description: "Failed to create ticket. Please try again.",
2471
- variant: "destructive"
2472
- });
2500
+ toast.error("Failed to create ticket. Please try again.");
2473
2501
  } finally {
2474
2502
  setIsSubmitting(false);
2475
2503
  }
@@ -2618,7 +2646,7 @@ var SupportLayout = () => {
2618
2646
  // package.json
2619
2647
  var package_default = {
2620
2648
  name: "@djangocfg/ext-support",
2621
- version: "1.0.9",
2649
+ version: "1.0.10",
2622
2650
  description: "Support ticket system extension for DjangoCFG",
2623
2651
  keywords: [
2624
2652
  "django",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ext-support",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "Support ticket system extension for DjangoCFG",
5
5
  "keywords": [
6
6
  "django",
@@ -59,9 +59,9 @@
59
59
  "check": "tsc --noEmit"
60
60
  },
61
61
  "peerDependencies": {
62
- "@djangocfg/api": "^2.1.104",
62
+ "@djangocfg/api": "^2.1.107",
63
63
  "@djangocfg/ext-base": "^1.0.8",
64
- "@djangocfg/ui-core": "^2.1.104",
64
+ "@djangocfg/ui-core": "^2.1.107",
65
65
  "consola": "^3.4.2",
66
66
  "lucide-react": "^0.545.0",
67
67
  "moment": "^2.30.1",
@@ -73,10 +73,10 @@
73
73
  "zod": "^4.1.13"
74
74
  },
75
75
  "devDependencies": {
76
- "@djangocfg/api": "^2.1.104",
76
+ "@djangocfg/api": "^2.1.107",
77
77
  "@djangocfg/ext-base": "^1.0.8",
78
- "@djangocfg/ui-core": "^2.1.104",
79
- "@djangocfg/typescript-config": "^2.1.104",
78
+ "@djangocfg/ui-core": "^2.1.107",
79
+ "@djangocfg/typescript-config": "^2.1.107",
80
80
  "@types/node": "^24.7.2",
81
81
  "@types/react": "^19.0.0",
82
82
  "consola": "^3.4.2",
@@ -69,12 +69,5 @@ openapi_client = OpenAPIClientConfig(
69
69
  )
70
70
  ```
71
71
 
72
- **Copy to Next.js** (if `nextjs_admin` configured):
73
- ```python
74
- nextjs_admin = NextJsAdminConfig(
75
- project_path="../frontend/apps/...",
76
- api_output_path="app/_lib/api/generated",
77
- )
78
- ```
79
-
80
72
  @see https://djangocfg.com/docs/features/api-generation
73
+
@@ -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_support_support: ExtSupportSupport;
@@ -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;
@@ -14,6 +14,8 @@ export interface HttpRequest {
14
14
  params?: Record<string, any>;
15
15
  /** FormData for file uploads (multipart/form-data) */
16
16
  formData?: FormData;
17
+ /** Binary data for octet-stream uploads */
18
+ binaryBody?: Blob | ArrayBuffer;
17
19
  }
18
20
 
19
21
  export interface HttpResponse<T = any> {
@@ -37,7 +39,7 @@ export interface HttpClientAdapter {
37
39
  */
38
40
  export class FetchAdapter implements HttpClientAdapter {
39
41
  async request<T = any>(request: HttpRequest): Promise<HttpResponse<T>> {
40
- const { method, url, headers, body, params, formData } = request;
42
+ const { method, url, headers, body, params, formData, binaryBody } = request;
41
43
 
42
44
  // Build URL with query params
43
45
  let finalUrl = url;
@@ -58,12 +60,16 @@ export class FetchAdapter implements HttpClientAdapter {
58
60
  const finalHeaders: Record<string, string> = { ...headers };
59
61
 
60
62
  // Determine body and content-type
61
- let requestBody: string | FormData | undefined;
63
+ let requestBody: string | FormData | Blob | ArrayBuffer | undefined;
62
64
 
63
65
  if (formData) {
64
66
  // For multipart/form-data, let browser set Content-Type with boundary
65
67
  requestBody = formData;
66
68
  // Don't set Content-Type - browser will set it with boundary
69
+ } else if (binaryBody) {
70
+ // Binary upload (application/octet-stream)
71
+ finalHeaders['Content-Type'] = 'application/octet-stream';
72
+ requestBody = binaryBody;
67
73
  } else if (body) {
68
74
  // JSON request
69
75
  finalHeaders['Content-Type'] = 'application/json';
@@ -133,10 +133,11 @@ export class API {
133
133
 
134
134
  this._loadTokensFromStorage();
135
135
 
136
- // Initialize APIClient
136
+ // Initialize APIClient with token getter for URL authentication
137
137
  this._client = new APIClient(this.baseUrl, {
138
138
  retryConfig: this.options?.retryConfig,
139
139
  loggerConfig: this.options?.loggerConfig,
140
+ tokenGetter: () => this.getToken(),
140
141
  });
141
142
 
142
143
  // Always inject auth header wrapper (reads token dynamically from storage)
@@ -155,6 +156,7 @@ export class API {
155
156
  this._client = new APIClient(this.baseUrl, {
156
157
  retryConfig: this.options?.retryConfig,
157
158
  loggerConfig: this.options?.loggerConfig,
159
+ tokenGetter: () => this.getToken(),
158
160
  });
159
161
 
160
162
  // Always inject auth header wrapper (reads token dynamically from storage)
package/src/api/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { createExtensionAPI } from '@djangocfg/ext-base/api';
1
+ import { createExtensionAPI, initializeExtensionAPI } from '@djangocfg/ext-base/api';
2
2
 
3
3
  /**
4
4
  * Support Extension API
@@ -6,5 +6,10 @@ import { createExtensionAPI } from '@djangocfg/ext-base/api';
6
6
  * Pre-configured API instance with shared authentication
7
7
  */
8
8
  import { API } from './generated/ext_support';
9
+ import { configureAPI } from './generated/ext_support/api-instance';
9
10
 
11
+ // Initialize global API singleton for hooks and fetchers
12
+ initializeExtensionAPI(configureAPI);
13
+
14
+ // Create instance for direct usage
10
15
  export const apiSupport = createExtensionAPI(API);
@@ -2,6 +2,9 @@
2
2
 
3
3
  import React, { createContext, ReactNode, useContext } from 'react';
4
4
 
5
+ // Initialize API before using hooks
6
+ import '../api';
7
+
5
8
  import {
6
9
  useCreateSupportTicketsCreate, useCreateSupportTicketsMessagesCreate,
7
10
  useDeleteSupportTicketsDestroy, useDeleteSupportTicketsMessagesDestroy,
@@ -45,17 +45,10 @@ export const CreateTicketDialog: React.FC = () => {
45
45
  try {
46
46
  await createTicket(data);
47
47
  form.reset();
48
- toast({
49
- title: 'Success',
50
- description: 'Support ticket created successfully',
51
- });
48
+ toast.success('Support ticket created successfully');
52
49
  } catch (error) {
53
50
  supportLogger.error('Failed to create ticket:', error);
54
- toast({
55
- title: 'Error',
56
- description: 'Failed to create ticket. Please try again.',
57
- variant: 'destructive',
58
- });
51
+ toast.error('Failed to create ticket. Please try again.');
59
52
  } finally {
60
53
  setIsSubmitting(false);
61
54
  }