@djangocfg/layouts 2.1.235 → 2.1.236

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/layouts",
3
- "version": "2.1.235",
3
+ "version": "2.1.236",
4
4
  "description": "Simple, straightforward layout components for Next.js - import and use with props",
5
5
  "keywords": [
6
6
  "layouts",
@@ -74,14 +74,14 @@
74
74
  "check": "tsc --noEmit"
75
75
  },
76
76
  "peerDependencies": {
77
- "@djangocfg/api": "^2.1.235",
78
- "@djangocfg/centrifugo": "^2.1.235",
79
- "@djangocfg/i18n": "^2.1.235",
80
- "@djangocfg/monitor": "^2.1.235",
81
- "@djangocfg/debuger": "^2.1.235",
82
- "@djangocfg/ui-core": "^2.1.235",
83
- "@djangocfg/ui-nextjs": "^2.1.235",
84
- "@djangocfg/ui-tools": "^2.1.235",
77
+ "@djangocfg/api": "^2.1.236",
78
+ "@djangocfg/centrifugo": "^2.1.236",
79
+ "@djangocfg/i18n": "^2.1.236",
80
+ "@djangocfg/monitor": "^2.1.236",
81
+ "@djangocfg/debuger": "^2.1.236",
82
+ "@djangocfg/ui-core": "^2.1.236",
83
+ "@djangocfg/ui-nextjs": "^2.1.236",
84
+ "@djangocfg/ui-tools": "^2.1.236",
85
85
  "@hookform/resolvers": "^5.2.2",
86
86
  "consola": "^3.4.2",
87
87
  "lucide-react": "^0.545.0",
@@ -109,15 +109,15 @@
109
109
  "uuid": "^11.1.0"
110
110
  },
111
111
  "devDependencies": {
112
- "@djangocfg/api": "^2.1.235",
113
- "@djangocfg/i18n": "^2.1.235",
114
- "@djangocfg/centrifugo": "^2.1.235",
115
- "@djangocfg/monitor": "^2.1.235",
116
- "@djangocfg/debuger": "^2.1.235",
117
- "@djangocfg/typescript-config": "^2.1.235",
118
- "@djangocfg/ui-core": "^2.1.235",
119
- "@djangocfg/ui-nextjs": "^2.1.235",
120
- "@djangocfg/ui-tools": "^2.1.235",
112
+ "@djangocfg/api": "^2.1.236",
113
+ "@djangocfg/i18n": "^2.1.236",
114
+ "@djangocfg/centrifugo": "^2.1.236",
115
+ "@djangocfg/monitor": "^2.1.236",
116
+ "@djangocfg/debuger": "^2.1.236",
117
+ "@djangocfg/typescript-config": "^2.1.236",
118
+ "@djangocfg/ui-core": "^2.1.236",
119
+ "@djangocfg/ui-nextjs": "^2.1.236",
120
+ "@djangocfg/ui-tools": "^2.1.236",
121
121
  "@types/node": "^24.7.2",
122
122
  "@types/react": "^19.1.0",
123
123
  "@types/react-dom": "^19.1.0",
@@ -34,6 +34,7 @@ import { toast } from '@djangocfg/ui-core/hooks';
34
34
  import { useDebugMode, isProduction } from '@djangocfg/monitor/client';
35
35
 
36
36
  import { createErrorToast } from '../components/ErrorToast';
37
+ import { generateShortErrorId, getProductionToast } from '../utils/formatters';
37
38
  import {
38
39
  DEFAULT_CENTRIFUGO_CONFIG,
39
40
  DEFAULT_CORS_CONFIG,
@@ -148,6 +149,20 @@ function shouldShowError(detail: ErrorDetail): boolean {
148
149
  return true;
149
150
  }
150
151
 
152
+ /** Throttle production toasts — max 1 per type within this window */
153
+ const PROD_TOAST_THROTTLE_MS = 3000;
154
+ let lastProdToastByType: Map<string, number> | null = null;
155
+
156
+ function shouldThrottleProdToast(type: string): boolean {
157
+ if (typeof window === 'undefined') return false;
158
+ if (!lastProdToastByType) lastProdToastByType = new Map();
159
+ const now = Date.now();
160
+ const last = lastProdToastByType.get(type);
161
+ if (last && now - last < PROD_TOAST_THROTTLE_MS) return true;
162
+ lastProdToastByType.set(type, now);
163
+ return false;
164
+ }
165
+
151
166
  export interface ErrorTrackingProviderProps {
152
167
  children: ReactNode;
153
168
  validation?: Partial<ValidationErrorConfig>;
@@ -255,13 +270,19 @@ export function ErrorTrackingProvider({
255
270
  // Call custom error handler
256
271
  const shouldShowToast = onError?.(detail) !== false;
257
272
 
258
- // Show toast notification using Sonner (only for unique errors, and only in debug mode on production)
259
- if (config.showToast && shouldShowToast && isUnique && (!isProduction || isDebugMode)) {
260
- const toastOptions = createErrorToast(detail, config);
261
- toast.error(toastOptions.title, {
262
- description: toastOptions.description,
263
- duration: toastOptions.duration,
264
- });
273
+ // Show toast notification (only for unique errors)
274
+ if (config.showToast && shouldShowToast && isUnique) {
275
+ if (!isProduction || isDebugMode) {
276
+ const toastOptions = createErrorToast(detail, config);
277
+ toast.error(toastOptions.title, {
278
+ description: toastOptions.description,
279
+ duration: toastOptions.duration,
280
+ });
281
+ } else if (!shouldThrottleProdToast(detail.type)) {
282
+ const errorId = generateShortErrorId();
283
+ const { title, description } = getProductionToast(detail, errorId);
284
+ toast.error(title, { description, duration: config.duration });
285
+ }
265
286
  }
266
287
  },
267
288
  [onError, onMonitorCapture, isProduction, isDebugMode]
@@ -154,7 +154,7 @@ export function errorDetailToMonitorEvent(detail: ErrorDetail): MonitorEvent {
154
154
  }
155
155
 
156
156
  /**
157
- * Format error title based on type
157
+ * Format error title based on type (dev/debug mode — full details)
158
158
  */
159
159
  export function formatErrorTitle(detail: ValidationErrorDetail | CORSErrorDetail | NetworkErrorDetail | CentrifugoErrorDetail | RuntimeErrorDetail): string {
160
160
  switch (detail.type) {
@@ -176,3 +176,72 @@ export function formatErrorTitle(detail: ValidationErrorDetail | CORSErrorDetail
176
176
  return '❌ Error';
177
177
  }
178
178
  }
179
+
180
+ /**
181
+ * Generate short error ID for support reference
182
+ * Format: ERR-XXXXX (base36, derived from timestamp + counter)
183
+ */
184
+ let prodErrorCounter = 0;
185
+ export function generateShortErrorId(): string {
186
+ const id = ((Date.now() % 1_000_000) + (++prodErrorCounter)).toString(36).toUpperCase().slice(-5);
187
+ return `ERR-${id.padStart(5, '0')}`;
188
+ }
189
+
190
+ /**
191
+ * Production-safe toast content — no internal details exposed
192
+ * Returns user-friendly title + description with error ID for support
193
+ */
194
+ export function getProductionToast(detail: ErrorDetail, errorId: string): { title: string; description: string } {
195
+ switch (detail.type) {
196
+ case 'validation':
197
+ return {
198
+ title: 'Invalid data received',
199
+ description: `Please try again. Reference: ${errorId}`,
200
+ };
201
+ case 'cors':
202
+ return {
203
+ title: 'Server unavailable',
204
+ description: `Unable to reach the server. Reference: ${errorId}`,
205
+ };
206
+ case 'network': {
207
+ const status = detail.statusCode;
208
+ if (status && status >= 500) {
209
+ return {
210
+ title: 'Server error',
211
+ description: `We're working on it. Reference: ${errorId}`,
212
+ };
213
+ }
214
+ if (status === 403) {
215
+ return {
216
+ title: 'Access denied',
217
+ description: `You don't have permission. Reference: ${errorId}`,
218
+ };
219
+ }
220
+ if (status === 404) {
221
+ return {
222
+ title: 'Not found',
223
+ description: `The requested resource was not found. Reference: ${errorId}`,
224
+ };
225
+ }
226
+ return {
227
+ title: 'Connection error',
228
+ description: `Please check your connection and try again. Reference: ${errorId}`,
229
+ };
230
+ }
231
+ case 'centrifugo':
232
+ return {
233
+ title: 'Real-time connection error',
234
+ description: `Live updates may be delayed. Reference: ${errorId}`,
235
+ };
236
+ case 'runtime':
237
+ return {
238
+ title: 'Something went wrong',
239
+ description: `An unexpected error occurred. Reference: ${errorId}`,
240
+ };
241
+ default:
242
+ return {
243
+ title: 'Something went wrong',
244
+ description: `Please try again. Reference: ${errorId}`,
245
+ };
246
+ }
247
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Frontend configuration from environment variables
3
3
  */
4
- import { isDev } from '@djangocfg/ui-core/lib';
4
+ import { isDev, isProd } from '@djangocfg/ui-core/lib';
5
5
 
6
6
  export const config = {
7
7
  /**
@@ -9,4 +9,5 @@ export const config = {
9
9
  * In development mode, any OTP code is accepted for authentication
10
10
  */
11
11
  isDevelopment: isDev,
12
+ isProduction: isProd,
12
13
  } as const;