@djangocfg/ext-support 1.0.8 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.cjs +2 -2
- package/dist/config.js +2 -2
- package/dist/hooks.cjs +91 -62
- package/dist/hooks.js +49 -20
- package/dist/index.cjs +91 -62
- package/dist/index.d.cts +35 -12
- package/dist/index.d.ts +35 -12
- package/dist/index.js +49 -20
- 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/SupportLayout.tsx +1 -1
- package/src/layouts/SupportLayout/components/CreateTicketDialog.tsx +3 -10
- package/src/layouts/SupportLayout/components/MessageInput.tsx +2 -1
- package/src/layouts/SupportLayout/components/MessageList.tsx +1 -1
- package/src/layouts/SupportLayout/components/TicketCard.tsx +1 -1
- package/src/layouts/SupportLayout/components/TicketList.tsx +1 -1
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,16 +1,17 @@
|
|
|
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
|
-
import { Button, ResizablePanelGroup, ResizablePanel, ResizableHandle, Skeleton, ScrollArea,
|
|
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';
|
|
8
8
|
import useSWR, { useSWRConfig } from 'swr';
|
|
9
9
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
10
10
|
import moment2 from 'moment';
|
|
11
11
|
import { cn } from '@djangocfg/ui-core/lib';
|
|
12
12
|
import { useAuth } from '@djangocfg/api/auth';
|
|
13
13
|
import useSWRInfinite from 'swr/infinite';
|
|
14
|
+
import { useToast } from '@djangocfg/ui-core/hooks';
|
|
14
15
|
import { useForm } from 'react-hook-form';
|
|
15
16
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
16
17
|
import { createExtensionConfig } from '@djangocfg/ext-base';
|
|
@@ -152,7 +153,7 @@ var models_exports = {};
|
|
|
152
153
|
// src/api/generated/ext_support/http.ts
|
|
153
154
|
var FetchAdapter = class {
|
|
154
155
|
async request(request) {
|
|
155
|
-
const { method, url, headers, body, params, formData } = request;
|
|
156
|
+
const { method, url, headers, body, params, formData, binaryBody } = request;
|
|
156
157
|
let finalUrl = url;
|
|
157
158
|
if (params) {
|
|
158
159
|
const searchParams = new URLSearchParams();
|
|
@@ -170,6 +171,9 @@ var FetchAdapter = class {
|
|
|
170
171
|
let requestBody;
|
|
171
172
|
if (formData) {
|
|
172
173
|
requestBody = formData;
|
|
174
|
+
} else if (binaryBody) {
|
|
175
|
+
finalHeaders["Content-Type"] = "application/octet-stream";
|
|
176
|
+
requestBody = binaryBody;
|
|
173
177
|
} else if (body) {
|
|
174
178
|
finalHeaders["Content-Type"] = "application/json";
|
|
175
179
|
requestBody = JSON.stringify(body);
|
|
@@ -494,11 +498,13 @@ var APIClient = class {
|
|
|
494
498
|
httpClient;
|
|
495
499
|
logger = null;
|
|
496
500
|
retryConfig = null;
|
|
501
|
+
tokenGetter = null;
|
|
497
502
|
// Sub-clients
|
|
498
503
|
ext_support_support;
|
|
499
504
|
constructor(baseUrl, options) {
|
|
500
505
|
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
501
506
|
this.httpClient = options?.httpClient || new FetchAdapter();
|
|
507
|
+
this.tokenGetter = options?.tokenGetter || null;
|
|
502
508
|
if (options?.loggerConfig !== void 0) {
|
|
503
509
|
this.logger = new APILogger(options.loggerConfig);
|
|
504
510
|
}
|
|
@@ -521,6 +527,19 @@ var APIClient = class {
|
|
|
521
527
|
}
|
|
522
528
|
return null;
|
|
523
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
|
+
}
|
|
524
543
|
/**
|
|
525
544
|
* Make HTTP request with Django CSRF and session handling.
|
|
526
545
|
* Automatically retries on network errors and 5xx server errors.
|
|
@@ -551,7 +570,7 @@ var APIClient = class {
|
|
|
551
570
|
const headers = {
|
|
552
571
|
...options?.headers || {}
|
|
553
572
|
};
|
|
554
|
-
if (!options?.formData && !headers["Content-Type"]) {
|
|
573
|
+
if (!options?.formData && !options?.binaryBody && !headers["Content-Type"]) {
|
|
555
574
|
headers["Content-Type"] = "application/json";
|
|
556
575
|
}
|
|
557
576
|
if (this.logger) {
|
|
@@ -570,7 +589,8 @@ var APIClient = class {
|
|
|
570
589
|
headers,
|
|
571
590
|
params: options?.params,
|
|
572
591
|
body: options?.body,
|
|
573
|
-
formData: options?.formData
|
|
592
|
+
formData: options?.formData,
|
|
593
|
+
binaryBody: options?.binaryBody
|
|
574
594
|
});
|
|
575
595
|
const duration = Date.now() - startTime;
|
|
576
596
|
if (response.status >= 400) {
|
|
@@ -984,15 +1004,28 @@ __export(fetchers_exports, {
|
|
|
984
1004
|
|
|
985
1005
|
// src/api/generated/ext_support/api-instance.ts
|
|
986
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
|
+
}
|
|
987
1018
|
function getAPIInstance() {
|
|
1019
|
+
tryAutoConfigureFromEnv();
|
|
988
1020
|
if (!globalAPI) {
|
|
989
1021
|
throw new Error(
|
|
990
|
-
'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'
|
|
991
1023
|
);
|
|
992
1024
|
}
|
|
993
1025
|
return globalAPI;
|
|
994
1026
|
}
|
|
995
1027
|
function isAPIConfigured() {
|
|
1028
|
+
tryAutoConfigureFromEnv();
|
|
996
1029
|
return globalAPI !== null;
|
|
997
1030
|
}
|
|
998
1031
|
function configureAPI(config) {
|
|
@@ -1475,7 +1508,8 @@ var API = class {
|
|
|
1475
1508
|
this._loadTokensFromStorage();
|
|
1476
1509
|
this._client = new APIClient(this.baseUrl, {
|
|
1477
1510
|
retryConfig: this.options?.retryConfig,
|
|
1478
|
-
loggerConfig: this.options?.loggerConfig
|
|
1511
|
+
loggerConfig: this.options?.loggerConfig,
|
|
1512
|
+
tokenGetter: () => this.getToken()
|
|
1479
1513
|
});
|
|
1480
1514
|
this._injectAuthHeader();
|
|
1481
1515
|
this.ext_support_support = this._client.ext_support_support;
|
|
@@ -1487,7 +1521,8 @@ var API = class {
|
|
|
1487
1521
|
_reinitClients() {
|
|
1488
1522
|
this._client = new APIClient(this.baseUrl, {
|
|
1489
1523
|
retryConfig: this.options?.retryConfig,
|
|
1490
|
-
loggerConfig: this.options?.loggerConfig
|
|
1524
|
+
loggerConfig: this.options?.loggerConfig,
|
|
1525
|
+
tokenGetter: () => this.getToken()
|
|
1491
1526
|
});
|
|
1492
1527
|
this._injectAuthHeader();
|
|
1493
1528
|
this.ext_support_support = this._client.ext_support_support;
|
|
@@ -1578,6 +1613,7 @@ var API = class {
|
|
|
1578
1613
|
return "./schema.json";
|
|
1579
1614
|
}
|
|
1580
1615
|
};
|
|
1616
|
+
initializeExtensionAPI(configureAPI);
|
|
1581
1617
|
var apiSupport = createExtensionAPI(API);
|
|
1582
1618
|
function useSupportTicketsList(params, client) {
|
|
1583
1619
|
return useSWR(
|
|
@@ -2444,7 +2480,7 @@ var createTicketSchema = z.object({
|
|
|
2444
2480
|
});
|
|
2445
2481
|
var CreateTicketDialog = () => {
|
|
2446
2482
|
const { uiState, createTicket, closeCreateDialog } = useSupportLayoutContext();
|
|
2447
|
-
const { toast } = useToast();
|
|
2483
|
+
const { toast } = useToast$1();
|
|
2448
2484
|
const [isSubmitting, setIsSubmitting] = React7.useState(false);
|
|
2449
2485
|
const form = useForm({
|
|
2450
2486
|
resolver: zodResolver(createTicketSchema),
|
|
@@ -2458,17 +2494,10 @@ var CreateTicketDialog = () => {
|
|
|
2458
2494
|
try {
|
|
2459
2495
|
await createTicket(data);
|
|
2460
2496
|
form.reset();
|
|
2461
|
-
toast(
|
|
2462
|
-
title: "Success",
|
|
2463
|
-
description: "Support ticket created successfully"
|
|
2464
|
-
});
|
|
2497
|
+
toast.success("Support ticket created successfully");
|
|
2465
2498
|
} catch (error) {
|
|
2466
2499
|
supportLogger.error("Failed to create ticket:", error);
|
|
2467
|
-
toast(
|
|
2468
|
-
title: "Error",
|
|
2469
|
-
description: "Failed to create ticket. Please try again.",
|
|
2470
|
-
variant: "destructive"
|
|
2471
|
-
});
|
|
2500
|
+
toast.error("Failed to create ticket. Please try again.");
|
|
2472
2501
|
} finally {
|
|
2473
2502
|
setIsSubmitting(false);
|
|
2474
2503
|
}
|
|
@@ -2617,7 +2646,7 @@ var SupportLayout = () => {
|
|
|
2617
2646
|
// package.json
|
|
2618
2647
|
var package_default = {
|
|
2619
2648
|
name: "@djangocfg/ext-support",
|
|
2620
|
-
version: "1.0.
|
|
2649
|
+
version: "1.0.10",
|
|
2621
2650
|
description: "Support ticket system extension for DjangoCFG",
|
|
2622
2651
|
keywords: [
|
|
2623
2652
|
"django",
|
|
@@ -2679,7 +2708,6 @@ var package_default = {
|
|
|
2679
2708
|
"@djangocfg/api": "workspace:*",
|
|
2680
2709
|
"@djangocfg/ext-base": "workspace:*",
|
|
2681
2710
|
"@djangocfg/ui-core": "workspace:*",
|
|
2682
|
-
"@djangocfg/ui-nextjs": "workspace:*",
|
|
2683
2711
|
consola: "^3.4.2",
|
|
2684
2712
|
"lucide-react": "^0.545.0",
|
|
2685
2713
|
moment: "^2.30.1",
|
|
@@ -2693,6 +2721,7 @@ var package_default = {
|
|
|
2693
2721
|
devDependencies: {
|
|
2694
2722
|
"@djangocfg/api": "workspace:*",
|
|
2695
2723
|
"@djangocfg/ext-base": "workspace:*",
|
|
2724
|
+
"@djangocfg/ui-core": "workspace:*",
|
|
2696
2725
|
"@djangocfg/typescript-config": "workspace:*",
|
|
2697
2726
|
"@types/node": "^24.7.2",
|
|
2698
2727
|
"@types/react": "^19.0.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/ext-support",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "Support ticket system extension for DjangoCFG",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"django",
|
|
@@ -59,10 +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.
|
|
65
|
-
"@djangocfg/ui-nextjs": "^2.1.91",
|
|
64
|
+
"@djangocfg/ui-core": "^2.1.107",
|
|
66
65
|
"consola": "^3.4.2",
|
|
67
66
|
"lucide-react": "^0.545.0",
|
|
68
67
|
"moment": "^2.30.1",
|
|
@@ -74,9 +73,10 @@
|
|
|
74
73
|
"zod": "^4.1.13"
|
|
75
74
|
},
|
|
76
75
|
"devDependencies": {
|
|
77
|
-
"@djangocfg/api": "^2.1.
|
|
76
|
+
"@djangocfg/api": "^2.1.107",
|
|
78
77
|
"@djangocfg/ext-base": "^1.0.8",
|
|
79
|
-
"@djangocfg/
|
|
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;
|