@djangocfg/ext-newsletter 1.0.0

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.
Files changed (91) hide show
  1. package/README.md +140 -0
  2. package/dist/chunk-LQLPNWHR.js +2075 -0
  3. package/dist/hooks.cjs +2539 -0
  4. package/dist/hooks.d.cts +267 -0
  5. package/dist/hooks.d.ts +267 -0
  6. package/dist/hooks.js +370 -0
  7. package/dist/index.cjs +2153 -0
  8. package/dist/index.d.cts +2084 -0
  9. package/dist/index.d.ts +2084 -0
  10. package/dist/index.js +1 -0
  11. package/package.json +80 -0
  12. package/src/api/generated/ext_newsletter/_utils/fetchers/ext_newsletter__newsletter.ts +210 -0
  13. package/src/api/generated/ext_newsletter/_utils/fetchers/ext_newsletter__newsletter__bulk_email.ts +93 -0
  14. package/src/api/generated/ext_newsletter/_utils/fetchers/ext_newsletter__newsletter__campaigns.ts +338 -0
  15. package/src/api/generated/ext_newsletter/_utils/fetchers/ext_newsletter__newsletter__logs.ts +92 -0
  16. package/src/api/generated/ext_newsletter/_utils/fetchers/ext_newsletter__newsletter__newsletters.ts +150 -0
  17. package/src/api/generated/ext_newsletter/_utils/fetchers/ext_newsletter__newsletter__subscriptions.ts +210 -0
  18. package/src/api/generated/ext_newsletter/_utils/fetchers/ext_newsletter__newsletter__testing.ts +93 -0
  19. package/src/api/generated/ext_newsletter/_utils/fetchers/index.ts +34 -0
  20. package/src/api/generated/ext_newsletter/_utils/hooks/ext_newsletter__newsletter.ts +81 -0
  21. package/src/api/generated/ext_newsletter/_utils/hooks/ext_newsletter__newsletter__bulk_email.ts +42 -0
  22. package/src/api/generated/ext_newsletter/_utils/hooks/ext_newsletter__newsletter__campaigns.ts +130 -0
  23. package/src/api/generated/ext_newsletter/_utils/hooks/ext_newsletter__newsletter__logs.ts +37 -0
  24. package/src/api/generated/ext_newsletter/_utils/hooks/ext_newsletter__newsletter__newsletters.ts +52 -0
  25. package/src/api/generated/ext_newsletter/_utils/hooks/ext_newsletter__newsletter__subscriptions.ts +78 -0
  26. package/src/api/generated/ext_newsletter/_utils/hooks/ext_newsletter__newsletter__testing.ts +42 -0
  27. package/src/api/generated/ext_newsletter/_utils/hooks/index.ts +34 -0
  28. package/src/api/generated/ext_newsletter/_utils/schemas/BulkEmailRequest.schema.ts +26 -0
  29. package/src/api/generated/ext_newsletter/_utils/schemas/BulkEmailResponse.schema.ts +23 -0
  30. package/src/api/generated/ext_newsletter/_utils/schemas/EmailLog.schema.ts +31 -0
  31. package/src/api/generated/ext_newsletter/_utils/schemas/ErrorResponse.schema.ts +20 -0
  32. package/src/api/generated/ext_newsletter/_utils/schemas/Newsletter.schema.ts +26 -0
  33. package/src/api/generated/ext_newsletter/_utils/schemas/NewsletterCampaign.schema.ts +33 -0
  34. package/src/api/generated/ext_newsletter/_utils/schemas/NewsletterCampaignRequest.schema.ts +26 -0
  35. package/src/api/generated/ext_newsletter/_utils/schemas/NewsletterSubscription.schema.ts +27 -0
  36. package/src/api/generated/ext_newsletter/_utils/schemas/PaginatedEmailLogList.schema.ts +24 -0
  37. package/src/api/generated/ext_newsletter/_utils/schemas/PaginatedNewsletterCampaignList.schema.ts +24 -0
  38. package/src/api/generated/ext_newsletter/_utils/schemas/PaginatedNewsletterList.schema.ts +24 -0
  39. package/src/api/generated/ext_newsletter/_utils/schemas/PaginatedNewsletterSubscriptionList.schema.ts +24 -0
  40. package/src/api/generated/ext_newsletter/_utils/schemas/PatchedNewsletterCampaignRequest.schema.ts +26 -0
  41. package/src/api/generated/ext_newsletter/_utils/schemas/PatchedUnsubscribeRequest.schema.ts +19 -0
  42. package/src/api/generated/ext_newsletter/_utils/schemas/SendCampaignRequest.schema.ts +19 -0
  43. package/src/api/generated/ext_newsletter/_utils/schemas/SendCampaignResponse.schema.ts +22 -0
  44. package/src/api/generated/ext_newsletter/_utils/schemas/SubscribeRequest.schema.ts +20 -0
  45. package/src/api/generated/ext_newsletter/_utils/schemas/SubscribeResponse.schema.ts +21 -0
  46. package/src/api/generated/ext_newsletter/_utils/schemas/SuccessResponse.schema.ts +20 -0
  47. package/src/api/generated/ext_newsletter/_utils/schemas/TestEmailRequest.schema.ts +21 -0
  48. package/src/api/generated/ext_newsletter/_utils/schemas/Unsubscribe.schema.ts +19 -0
  49. package/src/api/generated/ext_newsletter/_utils/schemas/UnsubscribeRequest.schema.ts +19 -0
  50. package/src/api/generated/ext_newsletter/_utils/schemas/index.ts +40 -0
  51. package/src/api/generated/ext_newsletter/api-instance.ts +131 -0
  52. package/src/api/generated/ext_newsletter/client.ts +319 -0
  53. package/src/api/generated/ext_newsletter/enums.ts +24 -0
  54. package/src/api/generated/ext_newsletter/errors.ts +116 -0
  55. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter/client.ts +38 -0
  56. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter/index.ts +2 -0
  57. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter/models.ts +71 -0
  58. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__bulk_email/client.ts +24 -0
  59. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__bulk_email/index.ts +2 -0
  60. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__bulk_email/models.ts +29 -0
  61. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__campaigns/client.ts +85 -0
  62. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__campaigns/index.ts +2 -0
  63. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__campaigns/models.ts +100 -0
  64. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__logs/client.ts +35 -0
  65. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__logs/index.ts +2 -0
  66. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__logs/models.ts +51 -0
  67. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__newsletters/client.ts +45 -0
  68. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__newsletters/index.ts +2 -0
  69. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__newsletters/models.ts +42 -0
  70. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__subscriptions/client.ts +55 -0
  71. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__subscriptions/index.ts +2 -0
  72. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__subscriptions/models.ts +92 -0
  73. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__testing/client.ts +24 -0
  74. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__testing/index.ts +2 -0
  75. package/src/api/generated/ext_newsletter/ext_newsletter__newsletter__testing/models.ts +24 -0
  76. package/src/api/generated/ext_newsletter/http.ts +103 -0
  77. package/src/api/generated/ext_newsletter/index.ts +315 -0
  78. package/src/api/generated/ext_newsletter/logger.ts +259 -0
  79. package/src/api/generated/ext_newsletter/retry.ts +175 -0
  80. package/src/api/generated/ext_newsletter/schema.json +1739 -0
  81. package/src/api/generated/ext_newsletter/storage.ts +161 -0
  82. package/src/api/generated/ext_newsletter/validation-events.ts +133 -0
  83. package/src/api/index.ts +9 -0
  84. package/src/components/Hero/index.tsx +160 -0
  85. package/src/components/Hero/types.ts +37 -0
  86. package/src/config.ts +20 -0
  87. package/src/contexts/newsletter/NewsletterContext.tsx +264 -0
  88. package/src/contexts/newsletter/types.ts +32 -0
  89. package/src/hooks/index.ts +21 -0
  90. package/src/index.ts +14 -0
  91. package/src/utils/logger.ts +9 -0
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Zod schema for UnsubscribeRequest
3
+ *
4
+ * This schema provides runtime validation and type inference.
5
+ * * Simple serializer for unsubscribe.
6
+ * */
7
+ import { z } from 'zod'
8
+
9
+ /**
10
+ * Simple serializer for unsubscribe.
11
+ */
12
+ export const UnsubscribeRequestSchema = z.object({
13
+ subscription_id: z.int(),
14
+ })
15
+
16
+ /**
17
+ * Infer TypeScript type from Zod schema
18
+ */
19
+ export type UnsubscribeRequest = z.infer<typeof UnsubscribeRequestSchema>
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Zod Schemas - Runtime validation and type inference
3
+ *
4
+ * Auto-generated from OpenAPI specification.
5
+ * Provides runtime validation for API requests and responses.
6
+ *
7
+ * Usage:
8
+ * ```typescript
9
+ * import { UserSchema } from './schemas'
10
+ *
11
+ * // Validate data
12
+ * const user = UserSchema.parse(data)
13
+ *
14
+ * // Type inference
15
+ * type User = z.infer<typeof UserSchema>
16
+ * ```
17
+ */
18
+
19
+ export * from './BulkEmailRequest.schema'
20
+ export * from './BulkEmailResponse.schema'
21
+ export * from './EmailLog.schema'
22
+ export * from './ErrorResponse.schema'
23
+ export * from './Newsletter.schema'
24
+ export * from './NewsletterCampaign.schema'
25
+ export * from './NewsletterCampaignRequest.schema'
26
+ export * from './NewsletterSubscription.schema'
27
+ export * from './PaginatedEmailLogList.schema'
28
+ export * from './PaginatedNewsletterCampaignList.schema'
29
+ export * from './PaginatedNewsletterList.schema'
30
+ export * from './PaginatedNewsletterSubscriptionList.schema'
31
+ export * from './PatchedNewsletterCampaignRequest.schema'
32
+ export * from './PatchedUnsubscribeRequest.schema'
33
+ export * from './SendCampaignRequest.schema'
34
+ export * from './SendCampaignResponse.schema'
35
+ export * from './SubscribeRequest.schema'
36
+ export * from './SubscribeResponse.schema'
37
+ export * from './SuccessResponse.schema'
38
+ export * from './TestEmailRequest.schema'
39
+ export * from './Unsubscribe.schema'
40
+ export * from './UnsubscribeRequest.schema'
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Global API Instance - Singleton configuration
3
+ *
4
+ * This module provides a global API instance that can be configured once
5
+ * and used throughout your application.
6
+ *
7
+ * Usage:
8
+ * ```typescript
9
+ * // Configure once (e.g., in your app entry point)
10
+ * import { configureAPI } from './api-instance'
11
+ *
12
+ * configureAPI({
13
+ * baseUrl: 'https://api.example.com',
14
+ * token: 'your-jwt-token'
15
+ * })
16
+ *
17
+ * // Then use fetchers and hooks anywhere without configuration
18
+ * import { getUsers } from './fetchers'
19
+ * const users = await getUsers({ page: 1 })
20
+ * ```
21
+ *
22
+ * For SSR or multiple instances:
23
+ * ```typescript
24
+ * import { API } from './index'
25
+ * import { getUsers } from './fetchers'
26
+ *
27
+ * const api = new API('https://api.example.com')
28
+ * const users = await getUsers({ page: 1 }, api)
29
+ * ```
30
+ */
31
+
32
+ import { API, type APIOptions } from './index'
33
+
34
+ let globalAPI: API | null = null
35
+
36
+ /**
37
+ * Get the global API instance
38
+ * @throws Error if API is not configured
39
+ */
40
+ export function getAPIInstance(): API {
41
+ if (!globalAPI) {
42
+ throw new Error(
43
+ 'API not configured. Call configureAPI() with your base URL before using fetchers or hooks.\n\n' +
44
+ 'Example:\n' +
45
+ ' import { configureAPI } from "./api-instance"\n' +
46
+ ' configureAPI({ baseUrl: "https://api.example.com" })'
47
+ )
48
+ }
49
+ return globalAPI
50
+ }
51
+
52
+ /**
53
+ * Check if API is configured
54
+ */
55
+ export function isAPIConfigured(): boolean {
56
+ return globalAPI !== null
57
+ }
58
+
59
+ /**
60
+ * Configure the global API instance
61
+ *
62
+ * @param baseUrl - Base URL for the API
63
+ * @param options - Optional configuration (storage, retry, logger)
64
+ *
65
+ * @example
66
+ * ```typescript
67
+ * configureAPI({
68
+ * baseUrl: 'https://api.example.com',
69
+ * token: 'jwt-token',
70
+ * options: {
71
+ * retryConfig: { maxRetries: 3 },
72
+ * loggerConfig: { enabled: true }
73
+ * }
74
+ * })
75
+ * ```
76
+ */
77
+ export function configureAPI(config: {
78
+ baseUrl: string
79
+ token?: string
80
+ refreshToken?: string
81
+ options?: APIOptions
82
+ }): API {
83
+ globalAPI = new API(config.baseUrl, config.options)
84
+
85
+ if (config.token) {
86
+ globalAPI.setToken(config.token, config.refreshToken)
87
+ }
88
+
89
+ return globalAPI
90
+ }
91
+
92
+ /**
93
+ * Reconfigure the global API instance with new settings
94
+ * Useful for updating tokens or base URL
95
+ */
96
+ export function reconfigureAPI(updates: {
97
+ baseUrl?: string
98
+ token?: string
99
+ refreshToken?: string
100
+ }): API {
101
+ const instance = getAPIInstance()
102
+
103
+ if (updates.baseUrl) {
104
+ instance.setBaseUrl(updates.baseUrl)
105
+ }
106
+
107
+ if (updates.token) {
108
+ instance.setToken(updates.token, updates.refreshToken)
109
+ }
110
+
111
+ return instance
112
+ }
113
+
114
+ /**
115
+ * Clear tokens from the global API instance
116
+ */
117
+ export function clearAPITokens(): void {
118
+ const instance = getAPIInstance()
119
+ instance.clearTokens()
120
+ }
121
+
122
+ /**
123
+ * Reset the global API instance
124
+ * Useful for testing or logout scenarios
125
+ */
126
+ export function resetAPI(): void {
127
+ if (globalAPI) {
128
+ globalAPI.clearTokens()
129
+ }
130
+ globalAPI = null
131
+ }
@@ -0,0 +1,319 @@
1
+ import { ExtNewsletterBulkEmail } from "./ext_newsletter__newsletter__bulk_email";
2
+ import { ExtNewsletterCampaigns } from "./ext_newsletter__newsletter__campaigns";
3
+ import { ExtNewsletterLogs } from "./ext_newsletter__newsletter__logs";
4
+ import { ExtNewsletterNewsletters } from "./ext_newsletter__newsletter__newsletters";
5
+ import { ExtNewsletterSubscriptions } from "./ext_newsletter__newsletter__subscriptions";
6
+ import { ExtNewsletterTesting } from "./ext_newsletter__newsletter__testing";
7
+ import { ExtNewsletterNewsletter } from "./ext_newsletter__newsletter";
8
+ import { HttpClientAdapter, FetchAdapter } from "./http";
9
+ import { APIError, NetworkError } from "./errors";
10
+ import { APILogger, type LoggerConfig } from "./logger";
11
+ import { withRetry, type RetryConfig } from "./retry";
12
+
13
+
14
+ /**
15
+ * Async API client for Django CFG API.
16
+ *
17
+ * Usage:
18
+ * ```typescript
19
+ * const client = new APIClient('https://api.example.com');
20
+ * const users = await client.users.list();
21
+ * const post = await client.posts.create(newPost);
22
+ *
23
+ * // Custom HTTP adapter (e.g., Axios)
24
+ * const client = new APIClient('https://api.example.com', {
25
+ * httpClient: new AxiosAdapter()
26
+ * });
27
+ * ```
28
+ */
29
+ export class APIClient {
30
+ private baseUrl: string;
31
+ private httpClient: HttpClientAdapter;
32
+ private logger: APILogger | null = null;
33
+ private retryConfig: RetryConfig | null = null;
34
+
35
+ // Sub-clients
36
+ public ext_newsletter_bulk_email: ExtNewsletterBulkEmail;
37
+ public ext_newsletter_campaigns: ExtNewsletterCampaigns;
38
+ public ext_newsletter_logs: ExtNewsletterLogs;
39
+ public ext_newsletter_newsletters: ExtNewsletterNewsletters;
40
+ public ext_newsletter_subscriptions: ExtNewsletterSubscriptions;
41
+ public ext_newsletter_testing: ExtNewsletterTesting;
42
+ public ext_newsletter_newsletter: ExtNewsletterNewsletter;
43
+
44
+ constructor(
45
+ baseUrl: string,
46
+ options?: {
47
+ httpClient?: HttpClientAdapter;
48
+ loggerConfig?: Partial<LoggerConfig>;
49
+ retryConfig?: RetryConfig;
50
+ }
51
+ ) {
52
+ this.baseUrl = baseUrl.replace(/\/$/, '');
53
+ this.httpClient = options?.httpClient || new FetchAdapter();
54
+
55
+ // Initialize logger if config provided
56
+ if (options?.loggerConfig !== undefined) {
57
+ this.logger = new APILogger(options.loggerConfig);
58
+ }
59
+
60
+ // Store retry configuration
61
+ if (options?.retryConfig !== undefined) {
62
+ this.retryConfig = options.retryConfig;
63
+ }
64
+
65
+ // Initialize sub-clients
66
+ this.ext_newsletter_bulk_email = new ExtNewsletterBulkEmail(this);
67
+ this.ext_newsletter_campaigns = new ExtNewsletterCampaigns(this);
68
+ this.ext_newsletter_logs = new ExtNewsletterLogs(this);
69
+ this.ext_newsletter_newsletters = new ExtNewsletterNewsletters(this);
70
+ this.ext_newsletter_subscriptions = new ExtNewsletterSubscriptions(this);
71
+ this.ext_newsletter_testing = new ExtNewsletterTesting(this);
72
+ this.ext_newsletter_newsletter = new ExtNewsletterNewsletter(this);
73
+ }
74
+
75
+ /**
76
+ * Get CSRF token from cookies (for SessionAuthentication).
77
+ *
78
+ * Returns null if cookie doesn't exist (JWT-only auth).
79
+ */
80
+ getCsrfToken(): string | null {
81
+ const name = 'csrftoken';
82
+ const value = `; ${document.cookie}`;
83
+ const parts = value.split(`; ${name}=`);
84
+ if (parts.length === 2) {
85
+ return parts.pop()?.split(';').shift() || null;
86
+ }
87
+ return null;
88
+ }
89
+
90
+ /**
91
+ * Make HTTP request with Django CSRF and session handling.
92
+ * Automatically retries on network errors and 5xx server errors.
93
+ */
94
+ async request<T>(
95
+ method: string,
96
+ path: string,
97
+ options?: {
98
+ params?: Record<string, any>;
99
+ body?: any;
100
+ formData?: FormData;
101
+ headers?: Record<string, string>;
102
+ }
103
+ ): Promise<T> {
104
+ // Wrap request in retry logic if configured
105
+ if (this.retryConfig) {
106
+ return withRetry(() => this._makeRequest<T>(method, path, options), {
107
+ ...this.retryConfig,
108
+ onFailedAttempt: (info) => {
109
+ // Log retry attempts
110
+ if (this.logger) {
111
+ this.logger.warn(
112
+ `Retry attempt ${info.attemptNumber}/${info.retriesLeft + info.attemptNumber} ` +
113
+ `for ${method} ${path}: ${info.error.message}`
114
+ );
115
+ }
116
+ // Call user's onFailedAttempt if provided
117
+ this.retryConfig?.onFailedAttempt?.(info);
118
+ },
119
+ });
120
+ }
121
+
122
+ // No retry configured, make request directly
123
+ return this._makeRequest<T>(method, path, options);
124
+ }
125
+
126
+ /**
127
+ * Internal request method (without retry wrapper).
128
+ * Used by request() method with optional retry logic.
129
+ */
130
+ private async _makeRequest<T>(
131
+ method: string,
132
+ path: string,
133
+ options?: {
134
+ params?: Record<string, any>;
135
+ body?: any;
136
+ formData?: FormData;
137
+ headers?: Record<string, string>;
138
+ }
139
+ ): Promise<T> {
140
+ // Build URL - handle both absolute and relative paths
141
+ // When baseUrl is empty (static builds), path is used as-is (relative to current origin)
142
+ const url = this.baseUrl ? `${this.baseUrl}${path}` : path;
143
+ const startTime = Date.now();
144
+
145
+ // Build headers - start with custom headers from options
146
+ const headers: Record<string, string> = {
147
+ ...(options?.headers || {})
148
+ };
149
+
150
+ // Don't set Content-Type for FormData (browser will set it with boundary)
151
+ if (!options?.formData && !headers['Content-Type']) {
152
+ headers['Content-Type'] = 'application/json';
153
+ }
154
+
155
+ // CSRF not needed - SessionAuthentication not enabled in DRF config
156
+ // Your API uses JWT/Token authentication (no CSRF required)
157
+
158
+ // Log request
159
+ if (this.logger) {
160
+ this.logger.logRequest({
161
+ method,
162
+ url: url,
163
+ headers,
164
+ body: options?.formData || options?.body,
165
+ timestamp: startTime,
166
+ });
167
+ }
168
+
169
+ try {
170
+ // Make request via HTTP adapter
171
+ const response = await this.httpClient.request<T>({
172
+ method,
173
+ url: url,
174
+ headers,
175
+ params: options?.params,
176
+ body: options?.body,
177
+ formData: options?.formData,
178
+ });
179
+
180
+ const duration = Date.now() - startTime;
181
+
182
+ // Check for HTTP errors
183
+ if (response.status >= 400) {
184
+ const error = new APIError(
185
+ response.status,
186
+ response.statusText,
187
+ response.data,
188
+ url
189
+ );
190
+
191
+ // Log error
192
+ if (this.logger) {
193
+ this.logger.logError(
194
+ {
195
+ method,
196
+ url: url,
197
+ headers,
198
+ body: options?.formData || options?.body,
199
+ timestamp: startTime,
200
+ },
201
+ {
202
+ message: error.message,
203
+ statusCode: response.status,
204
+ duration,
205
+ timestamp: Date.now(),
206
+ }
207
+ );
208
+ }
209
+
210
+ throw error;
211
+ }
212
+
213
+ // Log successful response
214
+ if (this.logger) {
215
+ this.logger.logResponse(
216
+ {
217
+ method,
218
+ url: url,
219
+ headers,
220
+ body: options?.formData || options?.body,
221
+ timestamp: startTime,
222
+ },
223
+ {
224
+ status: response.status,
225
+ statusText: response.statusText,
226
+ data: response.data,
227
+ duration,
228
+ timestamp: Date.now(),
229
+ }
230
+ );
231
+ }
232
+
233
+ return response.data as T;
234
+ } catch (error) {
235
+ const duration = Date.now() - startTime;
236
+
237
+ // Re-throw APIError as-is
238
+ if (error instanceof APIError) {
239
+ throw error;
240
+ }
241
+
242
+ // Detect CORS errors and dispatch event
243
+ const isCORSError = error instanceof TypeError &&
244
+ (error.message.toLowerCase().includes('cors') ||
245
+ error.message.toLowerCase().includes('failed to fetch') ||
246
+ error.message.toLowerCase().includes('network request failed'));
247
+
248
+ // Log specific error type first
249
+ if (this.logger) {
250
+ if (isCORSError) {
251
+ this.logger.error(`🚫 CORS Error: ${method} ${url}`);
252
+ this.logger.error(` → ${error instanceof Error ? error.message : String(error)}`);
253
+ this.logger.error(` → Configure security_domains parameter on the server`);
254
+ } else {
255
+ this.logger.error(`⚠️ Network Error: ${method} ${url}`);
256
+ this.logger.error(` → ${error instanceof Error ? error.message : String(error)}`);
257
+ }
258
+ }
259
+
260
+ // Dispatch browser events
261
+ if (typeof window !== 'undefined') {
262
+ try {
263
+ if (isCORSError) {
264
+ // Dispatch CORS-specific error event
265
+ window.dispatchEvent(new CustomEvent('cors-error', {
266
+ detail: {
267
+ url: url,
268
+ method: method,
269
+ error: error instanceof Error ? error.message : String(error),
270
+ timestamp: new Date(),
271
+ },
272
+ bubbles: true,
273
+ cancelable: false,
274
+ }));
275
+ } else {
276
+ // Dispatch generic network error event
277
+ window.dispatchEvent(new CustomEvent('network-error', {
278
+ detail: {
279
+ url: url,
280
+ method: method,
281
+ error: error instanceof Error ? error.message : String(error),
282
+ timestamp: new Date(),
283
+ },
284
+ bubbles: true,
285
+ cancelable: false,
286
+ }));
287
+ }
288
+ } catch (eventError) {
289
+ // Silently fail - event dispatch should never crash the app
290
+ }
291
+ }
292
+
293
+ // Wrap other errors as NetworkError
294
+ const networkError = error instanceof Error
295
+ ? new NetworkError(error.message, url, error)
296
+ : new NetworkError('Unknown error', url);
297
+
298
+ // Detailed logging via logger.logError
299
+ if (this.logger) {
300
+ this.logger.logError(
301
+ {
302
+ method,
303
+ url: url,
304
+ headers,
305
+ body: options?.formData || options?.body,
306
+ timestamp: startTime,
307
+ },
308
+ {
309
+ message: networkError.message,
310
+ duration,
311
+ timestamp: Date.now(),
312
+ }
313
+ );
314
+ }
315
+
316
+ throw networkError;
317
+ }
318
+ }
319
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * * `pending` - Pending
3
+ * * `sent` - Sent
4
+ * * `failed` - Failed
5
+ */
6
+ export enum EmailLogStatus {
7
+ PENDING = "pending",
8
+ SENT = "sent",
9
+ FAILED = "failed",
10
+ }
11
+
12
+ /**
13
+ * * `draft` - Draft
14
+ * * `sending` - Sending
15
+ * * `sent` - Sent
16
+ * * `failed` - Failed
17
+ */
18
+ export enum NewsletterCampaignStatus {
19
+ DRAFT = "draft",
20
+ SENDING = "sending",
21
+ SENT = "sent",
22
+ FAILED = "failed",
23
+ }
24
+
@@ -0,0 +1,116 @@
1
+ /**
2
+ * API Error Classes
3
+ *
4
+ * Typed error classes with Django REST Framework support.
5
+ */
6
+
7
+ /**
8
+ * HTTP API Error with DRF field-specific validation errors.
9
+ *
10
+ * Usage:
11
+ * ```typescript
12
+ * try {
13
+ * await api.users.create(userData);
14
+ * } catch (error) {
15
+ * if (error instanceof APIError) {
16
+ * if (error.isValidationError) {
17
+ * console.log('Field errors:', error.fieldErrors);
18
+ * // { "email": ["Email already exists"], "username": ["Required"] }
19
+ * }
20
+ * }
21
+ * }
22
+ * ```
23
+ */
24
+ export class APIError extends Error {
25
+ constructor(
26
+ public statusCode: number,
27
+ public statusText: string,
28
+ public response: any,
29
+ public url: string,
30
+ message?: string
31
+ ) {
32
+ super(message || `HTTP ${statusCode}: ${statusText}`);
33
+ this.name = 'APIError';
34
+ }
35
+
36
+ /**
37
+ * Get error details from response.
38
+ * DRF typically returns: { "detail": "Error message" } or { "field": ["error1", "error2"] }
39
+ */
40
+ get details(): Record<string, any> | null {
41
+ if (typeof this.response === 'object' && this.response !== null) {
42
+ return this.response;
43
+ }
44
+ return null;
45
+ }
46
+
47
+ /**
48
+ * Get field-specific validation errors from DRF.
49
+ * Returns: { "field_name": ["error1", "error2"], ... }
50
+ */
51
+ get fieldErrors(): Record<string, string[]> | null {
52
+ const details = this.details;
53
+ if (!details) return null;
54
+
55
+ // DRF typically returns: { "field": ["error1", "error2"] }
56
+ const fieldErrors: Record<string, string[]> = {};
57
+ for (const [key, value] of Object.entries(details)) {
58
+ if (Array.isArray(value)) {
59
+ fieldErrors[key] = value;
60
+ }
61
+ }
62
+
63
+ return Object.keys(fieldErrors).length > 0 ? fieldErrors : null;
64
+ }
65
+
66
+ /**
67
+ * Get single error message from DRF.
68
+ * Checks for "detail", "message", or first field error.
69
+ */
70
+ get errorMessage(): string {
71
+ const details = this.details;
72
+ if (!details) return this.message;
73
+
74
+ // Check for "detail" field (common in DRF)
75
+ if (details.detail) {
76
+ return Array.isArray(details.detail) ? details.detail.join(', ') : String(details.detail);
77
+ }
78
+
79
+ // Check for "message" field
80
+ if (details.message) {
81
+ return String(details.message);
82
+ }
83
+
84
+ // Return first field error
85
+ const fieldErrors = this.fieldErrors;
86
+ if (fieldErrors) {
87
+ const firstField = Object.keys(fieldErrors)[0];
88
+ if (firstField) {
89
+ return `${firstField}: ${fieldErrors[firstField]?.join(', ')}`;
90
+ }
91
+ }
92
+
93
+ return this.message;
94
+ }
95
+
96
+ // Helper methods for common HTTP status codes
97
+ get isValidationError(): boolean { return this.statusCode === 400; }
98
+ get isAuthError(): boolean { return this.statusCode === 401; }
99
+ get isPermissionError(): boolean { return this.statusCode === 403; }
100
+ get isNotFoundError(): boolean { return this.statusCode === 404; }
101
+ get isServerError(): boolean { return this.statusCode >= 500 && this.statusCode < 600; }
102
+ }
103
+
104
+ /**
105
+ * Network Error (connection failed, timeout, etc.)
106
+ */
107
+ export class NetworkError extends Error {
108
+ constructor(
109
+ message: string,
110
+ public url: string,
111
+ public originalError?: Error
112
+ ) {
113
+ super(message);
114
+ this.name = 'NetworkError';
115
+ }
116
+ }
@@ -0,0 +1,38 @@
1
+ import * as Models from "./models";
2
+
3
+
4
+ /**
5
+ * API endpoints for Newsletter.
6
+ */
7
+ export class ExtNewsletterNewsletter {
8
+ private client: any;
9
+
10
+ constructor(client: any) {
11
+ this.client = client;
12
+ }
13
+
14
+ /**
15
+ * Retrieve, update, or delete a newsletter campaign.
16
+ */
17
+ async campaignsPartialUpdate(id: number, data?: Models.PatchedNewsletterCampaignRequest): Promise<Models.NewsletterCampaign> {
18
+ const response = await this.client.request('PATCH', `/cfg/newsletter/campaigns/${id}/`, { body: data });
19
+ return response;
20
+ }
21
+
22
+ /**
23
+ * Handle newsletter unsubscriptions.
24
+ */
25
+ async unsubscribeUpdate(data: Models.UnsubscribeRequest): Promise<Models.Unsubscribe> {
26
+ const response = await this.client.request('PUT', "/cfg/newsletter/unsubscribe/", { body: data });
27
+ return response;
28
+ }
29
+
30
+ /**
31
+ * Handle newsletter unsubscriptions.
32
+ */
33
+ async unsubscribePartialUpdate(data?: Models.PatchedUnsubscribeRequest): Promise<Models.Unsubscribe> {
34
+ const response = await this.client.request('PATCH', "/cfg/newsletter/unsubscribe/", { body: data });
35
+ return response;
36
+ }
37
+
38
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./client";
2
+ export * as Models from "./models";