@djangocfg/ext-support 1.0.9 → 1.0.11
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 +1 -1
- package/dist/config.js +1 -1
- package/dist/hooks.cjs +44 -16
- package/dist/hooks.js +45 -17
- package/dist/index.cjs +44 -16
- package/dist/index.d.cts +35 -12
- package/dist/index.d.ts +35 -12
- package/dist/index.js +45 -17
- package/package.json +6 -6
- package/src/api/generated/ext_support/CLAUDE.md +1 -8
- package/src/api/generated/ext_support/api-instance.ts +61 -13
- package/src/api/generated/ext_support/client.ts +23 -2
- package/src/api/generated/ext_support/http.ts +8 -2
- package/src/api/generated/ext_support/index.ts +3 -1
- package/src/api/index.ts +6 -1
- package/src/contexts/SupportContext.tsx +3 -0
- package/src/layouts/SupportLayout/components/CreateTicketDialog.tsx +2 -9
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.
|
|
30
|
+
version: "1.0.11",
|
|
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.
|
|
7
|
+
version: "1.0.11",
|
|
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.
|
|
2659
|
+
version: "1.0.11",
|
|
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.
|
|
2649
|
+
version: "1.0.11",
|
|
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.
|
|
2659
|
+
version: "1.0.11",
|
|
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
|
|
1108
|
-
*
|
|
1121
|
+
* This module provides a global API instance that auto-configures from
|
|
1122
|
+
* environment variables or can be configured manually.
|
|
1109
1123
|
*
|
|
1110
|
-
*
|
|
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
|
-
*
|
|
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
|
|
1108
|
-
*
|
|
1121
|
+
* This module provides a global API instance that auto-configures from
|
|
1122
|
+
* environment variables or can be configured manually.
|
|
1109
1123
|
*
|
|
1110
|
-
*
|
|
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
|
-
*
|
|
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.
|
|
2649
|
+
version: "1.0.11",
|
|
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.
|
|
3
|
+
"version": "1.0.11",
|
|
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.
|
|
62
|
+
"@djangocfg/api": "^2.1.107",
|
|
63
63
|
"@djangocfg/ext-base": "^1.0.8",
|
|
64
|
-
"@djangocfg/ui-core": "^2.1.
|
|
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.
|
|
76
|
+
"@djangocfg/api": "^2.1.107",
|
|
77
77
|
"@djangocfg/ext-base": "^1.0.8",
|
|
78
|
-
"@djangocfg/ui-core": "^2.1.
|
|
79
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
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
|
|
6
|
-
*
|
|
5
|
+
* This module provides a global API instance that auto-configures from
|
|
6
|
+
* environment variables or can be configured manually.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
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
|
-
*
|
|
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
|
}
|