@jmruthers/pace-core 0.5.120 → 0.5.123
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/{AuthService-D4646R4b.d.ts → AuthService-DYuQPJj6.d.ts} +0 -9
- package/dist/{DataTable-DGZDJUYM.js → DataTable-WTS4IRF2.js} +7 -8
- package/dist/{PublicLoadingSpinner-DgDWTFqn.d.ts → PublicLoadingSpinner-CaoRbHvJ.d.ts} +30 -4
- package/dist/{UnifiedAuthProvider-UACKFATV.js → UnifiedAuthProvider-6C47WIML.js} +3 -4
- package/dist/{chunk-D6BOFXYR.js → chunk-35ZDPMBM.js} +3 -3
- package/dist/{chunk-CGURJ27Z.js → chunk-4MXVZVNS.js} +2 -2
- package/dist/{chunk-ZYJ6O5CA.js → chunk-C43QIDN3.js} +2 -2
- package/dist/{chunk-VKOCWWVY.js → chunk-CX5M4ZAG.js} +1 -6
- package/dist/{chunk-VKOCWWVY.js.map → chunk-CX5M4ZAG.js.map} +1 -1
- package/dist/{chunk-HFBOFZ3Z.js → chunk-DHMFMXFV.js} +258 -243
- package/dist/chunk-DHMFMXFV.js.map +1 -0
- package/dist/{chunk-RIEJGKD3.js → chunk-ESJTIADP.js} +15 -6
- package/dist/{chunk-RIEJGKD3.js.map → chunk-ESJTIADP.js.map} +1 -1
- package/dist/{chunk-SMJZMKYN.js → chunk-GEVIB2UB.js} +43 -10
- package/dist/chunk-GEVIB2UB.js.map +1 -0
- package/dist/{chunk-TDNI6ZWL.js → chunk-IJOZZOGT.js} +7 -7
- package/dist/chunk-IJOZZOGT.js.map +1 -0
- package/dist/{chunk-GZRXOUBE.js → chunk-M6DDYFUD.js} +2 -2
- package/dist/chunk-M6DDYFUD.js.map +1 -0
- package/dist/{chunk-B4GZ2BXO.js → chunk-NZGLXZGP.js} +3 -3
- package/dist/{chunk-NZ32EONV.js → chunk-QWNJCQXZ.js} +2 -2
- package/dist/{chunk-FKFHZUGF.js → chunk-XN6GWKMV.js} +43 -56
- package/dist/chunk-XN6GWKMV.js.map +1 -0
- package/dist/{chunk-BHWIUEYH.js → chunk-ZBLK676C.js} +1 -61
- package/dist/chunk-ZBLK676C.js.map +1 -0
- package/dist/{chunk-QPI2CCBA.js → chunk-ZPJMYGEP.js} +149 -96
- package/dist/chunk-ZPJMYGEP.js.map +1 -0
- package/dist/components.d.ts +1 -1
- package/dist/components.js +11 -11
- package/dist/{formatting-B1jSqgl-.d.ts → formatting-DFcCxUEk.d.ts} +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +9 -8
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.js +19 -17
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +2 -3
- package/dist/rbac/index.js +7 -8
- package/dist/styles/index.d.ts +1 -1
- package/dist/styles/index.js +5 -3
- package/dist/theming/runtime.d.ts +73 -1
- package/dist/theming/runtime.js +5 -5
- package/dist/{usePublicRouteParams-BdF8bZgs.d.ts → usePublicRouteParams-Dyt1tzI9.d.ts} +60 -8
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +5 -5
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +6 -6
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +6 -6
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +7 -7
- package/docs/api/interfaces/PublicErrorBoundaryState.md +5 -5
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +7 -7
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +51 -12
- package/docs/api/interfaces/PublicPageLayoutProps.md +72 -12
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +140 -30
- package/docs/best-practices/README.md +1 -1
- package/docs/implementation-guides/datatable-filtering.md +313 -0
- package/docs/implementation-guides/datatable-rbac-usage.md +317 -0
- package/docs/implementation-guides/hierarchical-datatable.md +850 -0
- package/docs/implementation-guides/large-datasets.md +281 -0
- package/docs/implementation-guides/performance.md +403 -0
- package/docs/implementation-guides/public-pages.md +4 -4
- package/docs/migration/quick-migration-guide.md +320 -0
- package/docs/rbac/quick-start.md +16 -16
- package/docs/troubleshooting/README.md +4 -4
- package/docs/troubleshooting/cake-page-permission-guard-issue-summary.md +1 -1
- package/docs/troubleshooting/debugging.md +1117 -0
- package/docs/troubleshooting/migration.md +918 -0
- package/examples/public-pages/CorrectPublicPageImplementation.tsx +30 -30
- package/examples/public-pages/PublicEventPage.tsx +41 -41
- package/examples/public-pages/PublicPageApp.tsx +33 -33
- package/examples/public-pages/PublicPageUsageExample.tsx +30 -30
- package/package.json +4 -4
- package/src/__tests__/hooks/usePermissions.test.ts +265 -0
- package/src/components/DataTable/DataTable.test.tsx +9 -38
- package/src/components/DataTable/DataTable.tsx +0 -7
- package/src/components/DataTable/components/DataTableCore.tsx +66 -136
- package/src/components/DataTable/components/DataTableModals.tsx +25 -22
- package/src/components/DataTable/components/EditableRow.tsx +118 -42
- package/src/components/DataTable/components/UnifiedTableBody.tsx +129 -76
- package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +33 -14
- package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +17 -5
- package/src/components/DataTable/utils/exportUtils.ts +3 -2
- package/src/components/DataTable/utils/flexibleImport.ts +27 -6
- package/src/components/Dialog/Dialog.tsx +1 -1
- package/src/components/Dialog/README.md +24 -24
- package/src/components/Dialog/examples/BasicHtmlTest.tsx +2 -2
- package/src/components/Dialog/examples/DebugHtmlExample.tsx +6 -6
- package/src/components/Dialog/examples/HtmlDialogExample.tsx +2 -2
- package/src/components/Dialog/examples/SimpleHtmlTest.tsx +3 -3
- package/src/components/Dialog/examples/__tests__/SimpleHtmlTest.test.tsx +4 -4
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +12 -1
- package/src/components/PublicLayout/EventLogo.tsx +175 -0
- package/src/components/PublicLayout/PublicErrorBoundary.tsx +22 -18
- package/src/components/PublicLayout/PublicLoadingSpinner.tsx +22 -14
- package/src/components/PublicLayout/PublicPageHeader.tsx +133 -40
- package/src/components/PublicLayout/PublicPageLayout.tsx +75 -72
- package/src/components/PublicLayout/__tests__/PublicErrorBoundary.test.tsx +1 -1
- package/src/components/PublicLayout/__tests__/PublicLoadingSpinner.test.tsx +8 -8
- package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +23 -16
- package/src/components/PublicLayout/__tests__/PublicPageLayout.test.tsx +86 -14
- package/src/examples/CorrectPublicPageImplementation.tsx +30 -30
- package/src/examples/PublicEventPage.tsx +41 -41
- package/src/examples/PublicPageApp.tsx +33 -33
- package/src/examples/PublicPageUsageExample.tsx +30 -30
- package/src/hooks/__tests__/usePublicEvent.unit.test.ts +583 -0
- package/src/hooks/__tests__/usePublicRouteParams.unit.test.ts +10 -3
- package/src/hooks/index.ts +1 -1
- package/src/hooks/public/usePublicEventLogo.ts +285 -0
- package/src/hooks/public/usePublicRouteParams.ts +21 -4
- package/src/hooks/useEventTheme.test.ts +119 -43
- package/src/hooks/useEventTheme.ts +84 -55
- package/src/index.ts +3 -1
- package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +630 -0
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +667 -0
- package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +647 -0
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +496 -0
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +496 -0
- package/src/rbac/secureClient.ts +4 -2
- package/src/services/EventService.ts +0 -66
- package/src/services/__tests__/EventService.eventColours.test.ts +44 -40
- package/src/styles/index.ts +1 -1
- package/src/theming/__tests__/parseEventColours.test.ts +209 -0
- package/src/theming/parseEventColours.ts +123 -0
- package/src/theming/runtime.ts +3 -0
- package/src/types/__tests__/file-reference.test.ts +447 -0
- package/src/types/database.generated.ts +1515 -424
- package/src/utils/formatDate.test.ts +11 -11
- package/src/utils/formatting.ts +3 -2
- package/dist/chunk-BHWIUEYH.js.map +0 -1
- package/dist/chunk-FKFHZUGF.js.map +0 -1
- package/dist/chunk-GZRXOUBE.js.map +0 -1
- package/dist/chunk-HFBOFZ3Z.js.map +0 -1
- package/dist/chunk-QPI2CCBA.js.map +0 -1
- package/dist/chunk-SMJZMKYN.js.map +0 -1
- package/dist/chunk-TDNI6ZWL.js.map +0 -1
- package/src/styles/semantic.css +0 -24
- /package/dist/{DataTable-DGZDJUYM.js.map → DataTable-WTS4IRF2.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-UACKFATV.js.map → UnifiedAuthProvider-6C47WIML.js.map} +0 -0
- /package/dist/{chunk-D6BOFXYR.js.map → chunk-35ZDPMBM.js.map} +0 -0
- /package/dist/{chunk-CGURJ27Z.js.map → chunk-4MXVZVNS.js.map} +0 -0
- /package/dist/{chunk-ZYJ6O5CA.js.map → chunk-C43QIDN3.js.map} +0 -0
- /package/dist/{chunk-B4GZ2BXO.js.map → chunk-NZGLXZGP.js.map} +0 -0
- /package/dist/{chunk-NZ32EONV.js.map → chunk-QWNJCQXZ.js.map} +0 -0
|
@@ -0,0 +1,1117 @@
|
|
|
1
|
+
# Debugging Guide
|
|
2
|
+
|
|
3
|
+
Effective debugging is essential for maintaining and troubleshooting `@jmruthers/pace-core` applications. This guide provides comprehensive debugging techniques and tools.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Debugging in `@jmruthers/pace-core` covers multiple areas:
|
|
8
|
+
|
|
9
|
+
- **Component Debugging**: React component issues and rendering problems
|
|
10
|
+
- **Hook Debugging**: Custom hook behavior and state management
|
|
11
|
+
- **Authentication Debugging**: User authentication and session issues
|
|
12
|
+
- **Permission Debugging**: RBAC and access control problems
|
|
13
|
+
- **API Debugging**: Supabase and external API issues
|
|
14
|
+
- **Performance Debugging**: Performance bottlenecks and memory leaks
|
|
15
|
+
|
|
16
|
+
## Debugging Tools
|
|
17
|
+
|
|
18
|
+
### 1. React Developer Tools
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// Enable React DevTools in development
|
|
22
|
+
if (process.env.NODE_ENV === 'development') {
|
|
23
|
+
// React DevTools will automatically connect
|
|
24
|
+
console.log('React DevTools enabled');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Custom hook for debugging component renders
|
|
28
|
+
function useDebugRender(componentName: string, props?: any) {
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
console.log(`${componentName} rendered with props:`, props);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
console.log(`${componentName} mounted`);
|
|
35
|
+
return () => console.log(`${componentName} unmounted`);
|
|
36
|
+
}, []);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Usage in components
|
|
40
|
+
function MyComponent({ data, loading }) {
|
|
41
|
+
useDebugRender('MyComponent', { data, loading });
|
|
42
|
+
|
|
43
|
+
if (loading) return <div>Loading...</div>;
|
|
44
|
+
return <div>{data}</div>;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 2. Custom Debug Hooks
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// hooks/useDebug.ts
|
|
52
|
+
import { useEffect, useRef } from 'react';
|
|
53
|
+
|
|
54
|
+
export function useDebugValue<T>(value: T, label: string) {
|
|
55
|
+
const prevValue = useRef<T>();
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (prevValue.current !== value) {
|
|
59
|
+
console.log(`${label} changed:`, {
|
|
60
|
+
previous: prevValue.current,
|
|
61
|
+
current: value,
|
|
62
|
+
});
|
|
63
|
+
prevValue.current = value;
|
|
64
|
+
}
|
|
65
|
+
}, [value, label]);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function useDebugEffect(effect: () => void, deps: any[], label: string) {
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
console.log(`${label} effect triggered with deps:`, deps);
|
|
71
|
+
effect();
|
|
72
|
+
}, deps);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function useDebugCallback<T extends (...args: any[]) => any>(
|
|
76
|
+
callback: T,
|
|
77
|
+
deps: any[],
|
|
78
|
+
label: string
|
|
79
|
+
): T {
|
|
80
|
+
const prevDeps = useRef<any[]>();
|
|
81
|
+
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (JSON.stringify(prevDeps.current) !== JSON.stringify(deps)) {
|
|
84
|
+
console.log(`${label} callback dependencies changed:`, deps);
|
|
85
|
+
prevDeps.current = deps;
|
|
86
|
+
}
|
|
87
|
+
}, [deps, label]);
|
|
88
|
+
|
|
89
|
+
return callback;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Usage
|
|
93
|
+
function MyComponent({ userId }) {
|
|
94
|
+
useDebugValue(userId, 'userId');
|
|
95
|
+
|
|
96
|
+
const handleClick = useDebugCallback(() => {
|
|
97
|
+
console.log('Button clicked');
|
|
98
|
+
}, [userId], 'handleClick');
|
|
99
|
+
|
|
100
|
+
return <button onClick={handleClick}>Click me</button>;
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 3. Error Boundary with Debugging
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// components/DebugErrorBoundary.tsx
|
|
108
|
+
import React from 'react';
|
|
109
|
+
import { ErrorBoundary } from '@jmruthers/pace-core';
|
|
110
|
+
|
|
111
|
+
interface DebugErrorBoundaryProps {
|
|
112
|
+
children: React.ReactNode;
|
|
113
|
+
fallback?: React.ComponentType<{ error: Error; resetError: () => void }>;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function DebugErrorBoundary({ children, fallback }: DebugErrorBoundaryProps) {
|
|
117
|
+
const handleError = (error: Error, errorInfo: React.ErrorInfo) => {
|
|
118
|
+
console.error('Error caught by boundary:', error);
|
|
119
|
+
console.error('Error info:', errorInfo);
|
|
120
|
+
|
|
121
|
+
// Send to error tracking service
|
|
122
|
+
if (typeof Sentry !== 'undefined') {
|
|
123
|
+
Sentry.captureException(error, {
|
|
124
|
+
extra: {
|
|
125
|
+
componentStack: errorInfo.componentStack,
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const FallbackComponent = fallback || DefaultFallback;
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<ErrorBoundary onError={handleError} fallback={FallbackComponent}>
|
|
135
|
+
{children}
|
|
136
|
+
</ErrorBoundary>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function DefaultFallback({ error, resetError }: { error: Error; resetError: () => void }) {
|
|
141
|
+
return (
|
|
142
|
+
<div className="error-boundary">
|
|
143
|
+
<h2>Something went wrong</h2>
|
|
144
|
+
<details>
|
|
145
|
+
<summary>Error details</summary>
|
|
146
|
+
<pre>{error.message}</pre>
|
|
147
|
+
<pre>{error.stack}</pre>
|
|
148
|
+
</details>
|
|
149
|
+
<button onClick={resetError}>Try again</button>
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Component Debugging
|
|
156
|
+
|
|
157
|
+
### 1. Component State Debugging
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// utils/componentDebugger.ts
|
|
161
|
+
export function debugComponentState<T>(
|
|
162
|
+
componentName: string,
|
|
163
|
+
state: T,
|
|
164
|
+
prevState?: T
|
|
165
|
+
) {
|
|
166
|
+
if (process.env.NODE_ENV === 'development') {
|
|
167
|
+
console.group(`${componentName} State Change`);
|
|
168
|
+
console.log('Previous State:', prevState);
|
|
169
|
+
console.log('Current State:', state);
|
|
170
|
+
console.log('Changes:', getStateChanges(prevState, state));
|
|
171
|
+
console.groupEnd();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function getStateChanges(prev: any, current: any): Record<string, any> {
|
|
176
|
+
if (!prev) return { initial: current };
|
|
177
|
+
|
|
178
|
+
const changes: Record<string, any> = {};
|
|
179
|
+
|
|
180
|
+
for (const key in current) {
|
|
181
|
+
if (prev[key] !== current[key]) {
|
|
182
|
+
changes[key] = {
|
|
183
|
+
from: prev[key],
|
|
184
|
+
to: current[key],
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return changes;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Usage in components
|
|
193
|
+
function MyComponent() {
|
|
194
|
+
const [state, setState] = useState({ count: 0, loading: false });
|
|
195
|
+
const prevState = useRef(state);
|
|
196
|
+
|
|
197
|
+
useEffect(() => {
|
|
198
|
+
debugComponentState('MyComponent', state, prevState.current);
|
|
199
|
+
prevState.current = state;
|
|
200
|
+
}, [state]);
|
|
201
|
+
|
|
202
|
+
return <div>{state.count}</div>;
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### 2. Props Debugging
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
// utils/propsDebugger.ts
|
|
210
|
+
export function debugProps<T>(componentName: string, props: T) {
|
|
211
|
+
if (process.env.NODE_ENV === 'development') {
|
|
212
|
+
console.log(`${componentName} props:`, props);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Higher-order component for automatic props debugging
|
|
217
|
+
export function withPropsDebug<P extends object>(
|
|
218
|
+
Component: React.ComponentType<P>,
|
|
219
|
+
componentName?: string
|
|
220
|
+
) {
|
|
221
|
+
const WrappedComponent = (props: P) => {
|
|
222
|
+
const name = componentName || Component.displayName || Component.name;
|
|
223
|
+
debugProps(name, props);
|
|
224
|
+
return <Component {...props} />;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
WrappedComponent.displayName = `withPropsDebug(${Component.displayName || Component.name})`;
|
|
228
|
+
return WrappedComponent;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Usage
|
|
232
|
+
const DebuggedButton = withPropsDebug(Button, 'Button');
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### 3. Render Performance Debugging
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
// utils/renderDebugger.ts
|
|
239
|
+
import { useComponentPerformance } from '@jmruthers/pace-core';
|
|
240
|
+
|
|
241
|
+
export function useRenderDebug(componentName: string) {
|
|
242
|
+
const { renderCount, averageRenderTime, lastRenderTime } = useComponentPerformance();
|
|
243
|
+
|
|
244
|
+
useEffect(() => {
|
|
245
|
+
if (process.env.NODE_ENV === 'development') {
|
|
246
|
+
console.log(`${componentName} render stats:`, {
|
|
247
|
+
renderCount,
|
|
248
|
+
averageRenderTime: averageRenderTime.toFixed(2),
|
|
249
|
+
lastRenderTime: lastRenderTime.toFixed(2),
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}, [renderCount, averageRenderTime, lastRenderTime, componentName]);
|
|
253
|
+
|
|
254
|
+
// Warn if component is rendering too frequently
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
if (renderCount > 10 && averageRenderTime > 16) {
|
|
257
|
+
console.warn(`${componentName} is rendering frequently and slowly:`, {
|
|
258
|
+
renderCount,
|
|
259
|
+
averageRenderTime: averageRenderTime.toFixed(2),
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}, [renderCount, averageRenderTime, componentName]);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Usage
|
|
266
|
+
function ExpensiveComponent() {
|
|
267
|
+
useRenderDebug('ExpensiveComponent');
|
|
268
|
+
|
|
269
|
+
// Component logic here
|
|
270
|
+
return <div>Expensive content</div>;
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Hook Debugging
|
|
275
|
+
|
|
276
|
+
### 1. Authentication Hook Debugging
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
// hooks/useAuthDebug.ts
|
|
280
|
+
import { useUnifiedAuth } from '@jmruthers/pace-core';
|
|
281
|
+
|
|
282
|
+
export function useAuthDebug() {
|
|
283
|
+
const auth = useUnifiedAuth();
|
|
284
|
+
|
|
285
|
+
useEffect(() => {
|
|
286
|
+
if (process.env.NODE_ENV === 'development') {
|
|
287
|
+
console.log('Auth state changed:', {
|
|
288
|
+
user: auth.user,
|
|
289
|
+
loading: auth.loading,
|
|
290
|
+
error: auth.error,
|
|
291
|
+
isAuthenticated: !!auth.user,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}, [auth.user, auth.loading, auth.error]);
|
|
295
|
+
|
|
296
|
+
// Debug authentication errors
|
|
297
|
+
useEffect(() => {
|
|
298
|
+
if (auth.error) {
|
|
299
|
+
console.error('Authentication error:', auth.error);
|
|
300
|
+
|
|
301
|
+
// Log additional context
|
|
302
|
+
console.log('Auth error context:', {
|
|
303
|
+
timestamp: new Date().toISOString(),
|
|
304
|
+
userAgent: navigator.userAgent,
|
|
305
|
+
url: window.location.href,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}, [auth.error]);
|
|
309
|
+
|
|
310
|
+
return auth;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Usage
|
|
314
|
+
function AuthDebugger() {
|
|
315
|
+
const auth = useAuthDebug();
|
|
316
|
+
|
|
317
|
+
if (auth.loading) return <div>Loading auth...</div>;
|
|
318
|
+
if (auth.error) return <div>Auth error: {auth.error}</div>;
|
|
319
|
+
if (!auth.user) return <div>Not authenticated</div>;
|
|
320
|
+
|
|
321
|
+
return <div>Authenticated as: {auth.user.email}</div>;
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### 2. RBAC Hook Debugging
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
// hooks/useRBACDebug.ts
|
|
329
|
+
import { useRBAC } from '@jmruthers/pace-core';
|
|
330
|
+
|
|
331
|
+
export function useRBACDebug() {
|
|
332
|
+
const rbac = useRBAC();
|
|
333
|
+
|
|
334
|
+
useEffect(() => {
|
|
335
|
+
if (process.env.NODE_ENV === 'development') {
|
|
336
|
+
console.log('RBAC state:', {
|
|
337
|
+
permissions: rbac.permissions,
|
|
338
|
+
roles: rbac.roles,
|
|
339
|
+
loading: rbac.loading,
|
|
340
|
+
error: rbac.error,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}, [rbac.permissions, rbac.roles, rbac.loading, rbac.error]);
|
|
344
|
+
|
|
345
|
+
// Debug permission checks
|
|
346
|
+
const debugHasPermission = useCallback((permission: string) => {
|
|
347
|
+
const hasPermission = rbac.hasPermission(permission);
|
|
348
|
+
|
|
349
|
+
if (process.env.NODE_ENV === 'development') {
|
|
350
|
+
console.log(`Permission check: ${permission} = ${hasPermission}`, {
|
|
351
|
+
userPermissions: rbac.permissions,
|
|
352
|
+
userRoles: rbac.roles,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return hasPermission;
|
|
357
|
+
}, [rbac]);
|
|
358
|
+
|
|
359
|
+
return {
|
|
360
|
+
...rbac,
|
|
361
|
+
hasPermission: debugHasPermission,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Usage
|
|
366
|
+
function PermissionDebugger() {
|
|
367
|
+
const rbac = useRBACDebug();
|
|
368
|
+
|
|
369
|
+
const canReadUsers = rbac.hasPermission('read:users');
|
|
370
|
+
const canWriteEvents = rbac.hasPermission('write:events');
|
|
371
|
+
|
|
372
|
+
return (
|
|
373
|
+
<div>
|
|
374
|
+
<div>Can read users: {canReadUsers ? 'Yes' : 'No'}</div>
|
|
375
|
+
<div>Can write events: {canWriteEvents ? 'Yes' : 'No'}</div>
|
|
376
|
+
</div>
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### 3. Event Hook Debugging
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
// hooks/useEventDebug.ts
|
|
385
|
+
import { useEvents } from '@jmruthers/pace-core';
|
|
386
|
+
|
|
387
|
+
export function useEventDebug() {
|
|
388
|
+
const events = useEvents();
|
|
389
|
+
|
|
390
|
+
useEffect(() => {
|
|
391
|
+
if (process.env.NODE_ENV === 'development') {
|
|
392
|
+
console.log('Events state:', {
|
|
393
|
+
events: events.events,
|
|
394
|
+
loading: events.loading,
|
|
395
|
+
error: events.error,
|
|
396
|
+
selectedEvent: events.selectedEvent,
|
|
397
|
+
count: events.events?.length || 0,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}, [events.events, events.loading, events.error, events.selectedEvent]);
|
|
401
|
+
|
|
402
|
+
// Debug event operations
|
|
403
|
+
const debugSetSelectedEvent = useCallback((event: any) => {
|
|
404
|
+
if (process.env.NODE_ENV === 'development') {
|
|
405
|
+
console.log('Setting selected event:', event);
|
|
406
|
+
}
|
|
407
|
+
events.setSelectedEvent(event);
|
|
408
|
+
}, [events.setSelectedEvent]);
|
|
409
|
+
|
|
410
|
+
const debugRefreshEvents = useCallback(async () => {
|
|
411
|
+
if (process.env.NODE_ENV === 'development') {
|
|
412
|
+
console.log('Refreshing events...');
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
try {
|
|
416
|
+
await events.refreshEvents();
|
|
417
|
+
console.log('Events refreshed successfully');
|
|
418
|
+
} catch (error) {
|
|
419
|
+
console.error('Failed to refresh events:', error);
|
|
420
|
+
}
|
|
421
|
+
}, [events.refreshEvents]);
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
...events,
|
|
425
|
+
setSelectedEvent: debugSetSelectedEvent,
|
|
426
|
+
refreshEvents: debugRefreshEvents,
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
## API Debugging
|
|
432
|
+
|
|
433
|
+
### 1. Supabase Debugging
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
// utils/supabaseDebugger.ts
|
|
437
|
+
import { useSupabase } from '@jmruthers/pace-core';
|
|
438
|
+
|
|
439
|
+
export function useSupabaseDebug() {
|
|
440
|
+
const { supabase } = useSupabase();
|
|
441
|
+
|
|
442
|
+
// Debug Supabase queries
|
|
443
|
+
const debugQuery = useCallback(async (table: string, query: any) => {
|
|
444
|
+
if (process.env.NODE_ENV === 'development') {
|
|
445
|
+
console.log(`Supabase query to ${table}:`, query);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
try {
|
|
449
|
+
const result = await supabase.from(table).select('*');
|
|
450
|
+
|
|
451
|
+
if (process.env.NODE_ENV === 'development') {
|
|
452
|
+
console.log(`Query result for ${table}:`, result);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return result;
|
|
456
|
+
} catch (error) {
|
|
457
|
+
console.error(`Query error for ${table}:`, error);
|
|
458
|
+
throw error;
|
|
459
|
+
}
|
|
460
|
+
}, [supabase]);
|
|
461
|
+
|
|
462
|
+
// Debug authentication
|
|
463
|
+
const debugAuth = useCallback(async (action: string, credentials: any) => {
|
|
464
|
+
if (process.env.NODE_ENV === 'development') {
|
|
465
|
+
console.log(`Auth action: ${action}`, credentials);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
try {
|
|
469
|
+
let result;
|
|
470
|
+
|
|
471
|
+
switch (action) {
|
|
472
|
+
case 'signIn':
|
|
473
|
+
result = await supabase.auth.signInWithPassword(credentials);
|
|
474
|
+
break;
|
|
475
|
+
case 'signUp':
|
|
476
|
+
result = await supabase.auth.signUp(credentials);
|
|
477
|
+
break;
|
|
478
|
+
case 'signOut':
|
|
479
|
+
result = await supabase.auth.signOut();
|
|
480
|
+
break;
|
|
481
|
+
default:
|
|
482
|
+
throw new Error(`Unknown auth action: ${action}`);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (process.env.NODE_ENV === 'development') {
|
|
486
|
+
console.log(`Auth result for ${action}:`, result);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return result;
|
|
490
|
+
} catch (error) {
|
|
491
|
+
console.error(`Auth error for ${action}:`, error);
|
|
492
|
+
throw error;
|
|
493
|
+
}
|
|
494
|
+
}, [supabase]);
|
|
495
|
+
|
|
496
|
+
return {
|
|
497
|
+
supabase,
|
|
498
|
+
debugQuery,
|
|
499
|
+
debugAuth,
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### 2. Network Request Debugging
|
|
505
|
+
|
|
506
|
+
```typescript
|
|
507
|
+
// utils/networkDebugger.ts
|
|
508
|
+
export function debugNetworkRequest(url: string, options: RequestInit) {
|
|
509
|
+
if (process.env.NODE_ENV === 'development') {
|
|
510
|
+
console.log('Network request:', {
|
|
511
|
+
url,
|
|
512
|
+
method: options.method || 'GET',
|
|
513
|
+
headers: options.headers,
|
|
514
|
+
body: options.body,
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
export function debugNetworkResponse(url: string, response: Response, data?: any) {
|
|
520
|
+
if (process.env.NODE_ENV === 'development') {
|
|
521
|
+
console.log('Network response:', {
|
|
522
|
+
url,
|
|
523
|
+
status: response.status,
|
|
524
|
+
statusText: response.statusText,
|
|
525
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
526
|
+
data,
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Custom fetch with debugging
|
|
532
|
+
export async function debugFetch(url: string, options: RequestInit = {}) {
|
|
533
|
+
debugNetworkRequest(url, options);
|
|
534
|
+
|
|
535
|
+
try {
|
|
536
|
+
const response = await fetch(url, options);
|
|
537
|
+
const data = await response.json();
|
|
538
|
+
|
|
539
|
+
debugNetworkResponse(url, response, data);
|
|
540
|
+
|
|
541
|
+
if (!response.ok) {
|
|
542
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return { response, data };
|
|
546
|
+
} catch (error) {
|
|
547
|
+
console.error('Network request failed:', {
|
|
548
|
+
url,
|
|
549
|
+
error: error.message,
|
|
550
|
+
});
|
|
551
|
+
throw error;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
## Performance Debugging
|
|
557
|
+
|
|
558
|
+
### 1. Memory Leak Detection
|
|
559
|
+
|
|
560
|
+
```typescript
|
|
561
|
+
// utils/memoryDebugger.ts
|
|
562
|
+
export function useMemoryLeakDetection(componentName: string) {
|
|
563
|
+
useEffect(() => {
|
|
564
|
+
const initialMemory = performance.memory?.usedJSHeapSize || 0;
|
|
565
|
+
|
|
566
|
+
return () => {
|
|
567
|
+
const finalMemory = performance.memory?.usedJSHeapSize || 0;
|
|
568
|
+
const memoryIncrease = finalMemory - initialMemory;
|
|
569
|
+
|
|
570
|
+
if (memoryIncrease > 1024 * 1024) { // 1MB threshold
|
|
571
|
+
console.warn(`${componentName} may have a memory leak:`, {
|
|
572
|
+
memoryIncrease: `${(memoryIncrease / 1024 / 1024).toFixed(2)}MB`,
|
|
573
|
+
initialMemory: `${(initialMemory / 1024 / 1024).toFixed(2)}MB`,
|
|
574
|
+
finalMemory: `${(finalMemory / 1024 / 1024).toFixed(2)}MB`,
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
}, [componentName]);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Usage
|
|
582
|
+
function MyComponent() {
|
|
583
|
+
useMemoryLeakDetection('MyComponent');
|
|
584
|
+
|
|
585
|
+
// Component logic
|
|
586
|
+
return <div>Content</div>;
|
|
587
|
+
}
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### 2. Performance Monitoring
|
|
591
|
+
|
|
592
|
+
```typescript
|
|
593
|
+
// utils/performanceDebugger.ts
|
|
594
|
+
export function usePerformanceDebug(componentName: string) {
|
|
595
|
+
const renderStartTime = useRef<number>();
|
|
596
|
+
|
|
597
|
+
useEffect(() => {
|
|
598
|
+
renderStartTime.current = performance.now();
|
|
599
|
+
|
|
600
|
+
return () => {
|
|
601
|
+
if (renderStartTime.current) {
|
|
602
|
+
const renderTime = performance.now() - renderStartTime.current;
|
|
603
|
+
|
|
604
|
+
if (renderTime > 16) { // 60fps threshold
|
|
605
|
+
console.warn(`${componentName} took ${renderTime.toFixed(2)}ms to render`);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
// Track re-renders
|
|
612
|
+
const renderCount = useRef(0);
|
|
613
|
+
useEffect(() => {
|
|
614
|
+
renderCount.current += 1;
|
|
615
|
+
|
|
616
|
+
if (renderCount.current > 10) {
|
|
617
|
+
console.warn(`${componentName} has rendered ${renderCount.current} times`);
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Performance observer for custom metrics
|
|
623
|
+
export function setupPerformanceObserver() {
|
|
624
|
+
if (typeof PerformanceObserver !== 'undefined') {
|
|
625
|
+
const observer = new PerformanceObserver((list) => {
|
|
626
|
+
for (const entry of list.getEntries()) {
|
|
627
|
+
if (entry.entryType === 'measure') {
|
|
628
|
+
console.log('Performance measure:', {
|
|
629
|
+
name: entry.name,
|
|
630
|
+
duration: entry.duration,
|
|
631
|
+
startTime: entry.startTime,
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
observer.observe({ entryTypes: ['measure'] });
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
## Error Debugging
|
|
643
|
+
|
|
644
|
+
### 1. Error Tracking and Analysis
|
|
645
|
+
|
|
646
|
+
```typescript
|
|
647
|
+
// utils/errorDebugger.ts
|
|
648
|
+
export class ErrorDebugger {
|
|
649
|
+
private static errors: Error[] = [];
|
|
650
|
+
private static maxErrors = 100;
|
|
651
|
+
|
|
652
|
+
static trackError(error: Error, context?: Record<string, any>) {
|
|
653
|
+
const errorInfo = {
|
|
654
|
+
error,
|
|
655
|
+
context,
|
|
656
|
+
timestamp: new Date().toISOString(),
|
|
657
|
+
stack: error.stack,
|
|
658
|
+
userAgent: navigator.userAgent,
|
|
659
|
+
url: window.location.href,
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
this.errors.push(error);
|
|
663
|
+
|
|
664
|
+
// Keep only the last maxErrors
|
|
665
|
+
if (this.errors.length > this.maxErrors) {
|
|
666
|
+
this.errors.shift();
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (process.env.NODE_ENV === 'development') {
|
|
670
|
+
console.error('Error tracked:', errorInfo);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
return errorInfo;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
static getErrors() {
|
|
677
|
+
return this.errors;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
static clearErrors() {
|
|
681
|
+
this.errors = [];
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
static getErrorStats() {
|
|
685
|
+
const errorTypes = this.errors.reduce((acc, error) => {
|
|
686
|
+
const type = error.constructor.name;
|
|
687
|
+
acc[type] = (acc[type] || 0) + 1;
|
|
688
|
+
return acc;
|
|
689
|
+
}, {} as Record<string, number>);
|
|
690
|
+
|
|
691
|
+
return {
|
|
692
|
+
totalErrors: this.errors.length,
|
|
693
|
+
errorTypes,
|
|
694
|
+
recentErrors: this.errors.slice(-10),
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Global error handler
|
|
700
|
+
export function setupGlobalErrorHandler() {
|
|
701
|
+
window.addEventListener('error', (event) => {
|
|
702
|
+
ErrorDebugger.trackError(event.error, {
|
|
703
|
+
filename: event.filename,
|
|
704
|
+
lineno: event.lineno,
|
|
705
|
+
colno: event.colno,
|
|
706
|
+
});
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
window.addEventListener('unhandledrejection', (event) => {
|
|
710
|
+
ErrorDebugger.trackError(new Error(event.reason), {
|
|
711
|
+
type: 'unhandledrejection',
|
|
712
|
+
});
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
### 2. Debug Console
|
|
718
|
+
|
|
719
|
+
```typescript
|
|
720
|
+
// utils/debugConsole.ts
|
|
721
|
+
export class DebugConsole {
|
|
722
|
+
private static logs: any[] = [];
|
|
723
|
+
private static maxLogs = 1000;
|
|
724
|
+
|
|
725
|
+
static log(message: string, data?: any) {
|
|
726
|
+
const logEntry = {
|
|
727
|
+
message,
|
|
728
|
+
data,
|
|
729
|
+
timestamp: new Date().toISOString(),
|
|
730
|
+
level: 'log',
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
this.logs.push(logEntry);
|
|
734
|
+
|
|
735
|
+
if (this.logs.length > this.maxLogs) {
|
|
736
|
+
this.logs.shift();
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
if (process.env.NODE_ENV === 'development') {
|
|
740
|
+
console.log(message, data);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
static warn(message: string, data?: any) {
|
|
745
|
+
const logEntry = {
|
|
746
|
+
message,
|
|
747
|
+
data,
|
|
748
|
+
timestamp: new Date().toISOString(),
|
|
749
|
+
level: 'warn',
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
this.logs.push(logEntry);
|
|
753
|
+
|
|
754
|
+
if (process.env.NODE_ENV === 'development') {
|
|
755
|
+
console.warn(message, data);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
static error(message: string, error?: Error, data?: any) {
|
|
760
|
+
const logEntry = {
|
|
761
|
+
message,
|
|
762
|
+
error,
|
|
763
|
+
data,
|
|
764
|
+
timestamp: new Date().toISOString(),
|
|
765
|
+
level: 'error',
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
this.logs.push(logEntry);
|
|
769
|
+
|
|
770
|
+
if (process.env.NODE_ENV === 'development') {
|
|
771
|
+
console.error(message, error, data);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
static getLogs() {
|
|
776
|
+
return this.logs;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
static clearLogs() {
|
|
780
|
+
this.logs = [];
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
static exportLogs() {
|
|
784
|
+
return {
|
|
785
|
+
logs: this.logs,
|
|
786
|
+
errorStats: ErrorDebugger.getErrorStats(),
|
|
787
|
+
timestamp: new Date().toISOString(),
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Make debug console available globally in development
|
|
793
|
+
if (process.env.NODE_ENV === 'development') {
|
|
794
|
+
(window as any).debugConsole = DebugConsole;
|
|
795
|
+
(window as any).errorDebugger = ErrorDebugger;
|
|
796
|
+
}
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
## Debugging Tools and Utilities
|
|
800
|
+
|
|
801
|
+
### 1. Debug Panel Component
|
|
802
|
+
|
|
803
|
+
```typescript
|
|
804
|
+
// components/DebugPanel.tsx
|
|
805
|
+
import { useState } from 'react';
|
|
806
|
+
import { DebugConsole, ErrorDebugger } from '../utils';
|
|
807
|
+
|
|
808
|
+
export function DebugPanel() {
|
|
809
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
810
|
+
const [activeTab, setActiveTab] = useState('logs');
|
|
811
|
+
|
|
812
|
+
if (process.env.NODE_ENV !== 'development') {
|
|
813
|
+
return null;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const logs = DebugConsole.getLogs();
|
|
817
|
+
const errorStats = ErrorDebugger.getErrorStats();
|
|
818
|
+
|
|
819
|
+
return (
|
|
820
|
+
<div className="debug-panel">
|
|
821
|
+
<button onClick={() => setIsOpen(!isOpen)}>
|
|
822
|
+
Debug Panel {isOpen ? '▼' : '▲'}
|
|
823
|
+
</button>
|
|
824
|
+
|
|
825
|
+
{isOpen && (
|
|
826
|
+
<div className="debug-content">
|
|
827
|
+
<div className="debug-tabs">
|
|
828
|
+
<button
|
|
829
|
+
className={activeTab === 'logs' ? 'active' : ''}
|
|
830
|
+
onClick={() => setActiveTab('logs')}
|
|
831
|
+
>
|
|
832
|
+
Logs ({logs.length})
|
|
833
|
+
</button>
|
|
834
|
+
<button
|
|
835
|
+
className={activeTab === 'errors' ? 'active' : ''}
|
|
836
|
+
onClick={() => setActiveTab('errors')}
|
|
837
|
+
>
|
|
838
|
+
Errors ({errorStats.totalErrors})
|
|
839
|
+
</button>
|
|
840
|
+
<button
|
|
841
|
+
className={activeTab === 'performance' ? 'active' : ''}
|
|
842
|
+
onClick={() => setActiveTab('performance')}
|
|
843
|
+
>
|
|
844
|
+
Performance
|
|
845
|
+
</button>
|
|
846
|
+
</div>
|
|
847
|
+
|
|
848
|
+
<div className="debug-content-area">
|
|
849
|
+
{activeTab === 'logs' && (
|
|
850
|
+
<div className="logs-tab">
|
|
851
|
+
{logs.slice(-20).map((log, index) => (
|
|
852
|
+
<div key={index} className={`log-entry ${log.level}`}>
|
|
853
|
+
<span className="timestamp">{log.timestamp}</span>
|
|
854
|
+
<span className="message">{log.message}</span>
|
|
855
|
+
{log.data && (
|
|
856
|
+
<pre className="data">{JSON.stringify(log.data, null, 2)}</pre>
|
|
857
|
+
)}
|
|
858
|
+
</div>
|
|
859
|
+
))}
|
|
860
|
+
</div>
|
|
861
|
+
)}
|
|
862
|
+
|
|
863
|
+
{activeTab === 'errors' && (
|
|
864
|
+
<div className="errors-tab">
|
|
865
|
+
<h3>Error Statistics</h3>
|
|
866
|
+
<pre>{JSON.stringify(errorStats, null, 2)}</pre>
|
|
867
|
+
|
|
868
|
+
<h3>Recent Errors</h3>
|
|
869
|
+
{errorStats.recentErrors.map((error, index) => (
|
|
870
|
+
<div key={index} className="error-entry">
|
|
871
|
+
<div className="error-message">{error.message}</div>
|
|
872
|
+
<div className="error-stack">{error.stack}</div>
|
|
873
|
+
</div>
|
|
874
|
+
))}
|
|
875
|
+
</div>
|
|
876
|
+
)}
|
|
877
|
+
|
|
878
|
+
{activeTab === 'performance' && (
|
|
879
|
+
<div className="performance-tab">
|
|
880
|
+
<h3>Memory Usage</h3>
|
|
881
|
+
<div>
|
|
882
|
+
Used: {(performance.memory?.usedJSHeapSize || 0) / 1024 / 1024}MB
|
|
883
|
+
</div>
|
|
884
|
+
<div>
|
|
885
|
+
Total: {(performance.memory?.totalJSHeapSize || 0) / 1024 / 1024}MB
|
|
886
|
+
</div>
|
|
887
|
+
<div>
|
|
888
|
+
Limit: {(performance.memory?.jsHeapSizeLimit || 0) / 1024 / 1024}MB
|
|
889
|
+
</div>
|
|
890
|
+
</div>
|
|
891
|
+
)}
|
|
892
|
+
</div>
|
|
893
|
+
|
|
894
|
+
<div className="debug-actions">
|
|
895
|
+
<button onClick={() => DebugConsole.clearLogs()}>Clear Logs</button>
|
|
896
|
+
<button onClick={() => ErrorDebugger.clearErrors()}>Clear Errors</button>
|
|
897
|
+
<button onClick={() => {
|
|
898
|
+
const data = DebugConsole.exportLogs();
|
|
899
|
+
const blob = new Blob([JSON.stringify(data, null, 2)], {
|
|
900
|
+
type: 'application/json',
|
|
901
|
+
});
|
|
902
|
+
const url = URL.createObjectURL(blob);
|
|
903
|
+
const a = document.createElement('a');
|
|
904
|
+
a.href = url;
|
|
905
|
+
a.download = `debug-logs-${new Date().toISOString()}.json`;
|
|
906
|
+
a.click();
|
|
907
|
+
}}>Export Logs</button>
|
|
908
|
+
</div>
|
|
909
|
+
</div>
|
|
910
|
+
)}
|
|
911
|
+
</div>
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
```
|
|
915
|
+
|
|
916
|
+
### 2. Debug Styles
|
|
917
|
+
|
|
918
|
+
```css
|
|
919
|
+
/* styles/debug.css */
|
|
920
|
+
.debug-panel {
|
|
921
|
+
position: fixed;
|
|
922
|
+
bottom: 0;
|
|
923
|
+
right: 0;
|
|
924
|
+
width: 400px;
|
|
925
|
+
max-height: 500px;
|
|
926
|
+
background: #1a1a1a;
|
|
927
|
+
color: #fff;
|
|
928
|
+
border: 1px solid #333;
|
|
929
|
+
z-index: 9999;
|
|
930
|
+
font-family: monospace;
|
|
931
|
+
font-size: 12px;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
.debug-content {
|
|
935
|
+
max-height: 400px;
|
|
936
|
+
overflow: hidden;
|
|
937
|
+
display: flex;
|
|
938
|
+
flex-direction: column;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
.debug-tabs {
|
|
942
|
+
display: flex;
|
|
943
|
+
border-bottom: 1px solid #333;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
.debug-tabs button {
|
|
947
|
+
flex: 1;
|
|
948
|
+
padding: 8px;
|
|
949
|
+
background: #2a2a2a;
|
|
950
|
+
border: none;
|
|
951
|
+
color: #fff;
|
|
952
|
+
cursor: pointer;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
.debug-tabs button.active {
|
|
956
|
+
background: #444;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
.debug-content-area {
|
|
960
|
+
flex: 1;
|
|
961
|
+
overflow-y: auto;
|
|
962
|
+
padding: 8px;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
.log-entry {
|
|
966
|
+
margin-bottom: 4px;
|
|
967
|
+
padding: 4px;
|
|
968
|
+
border-radius: 2px;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
.log-entry.error {
|
|
972
|
+
background: #4a1a1a;
|
|
973
|
+
color: #ff6b6b;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
.log-entry.warn {
|
|
977
|
+
background: #4a3a1a;
|
|
978
|
+
color: #ffd93d;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
.log-entry.log {
|
|
982
|
+
background: #1a4a1a;
|
|
983
|
+
color: #6bff6b;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
.timestamp {
|
|
987
|
+
color: #888;
|
|
988
|
+
margin-right: 8px;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
.data {
|
|
992
|
+
margin-top: 4px;
|
|
993
|
+
padding: 4px;
|
|
994
|
+
background: #2a2a2a;
|
|
995
|
+
border-radius: 2px;
|
|
996
|
+
font-size: 10px;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
.error-entry {
|
|
1000
|
+
margin-bottom: 8px;
|
|
1001
|
+
padding: 8px;
|
|
1002
|
+
background: #4a1a1a;
|
|
1003
|
+
border-radius: 4px;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
.error-message {
|
|
1007
|
+
color: #ff6b6b;
|
|
1008
|
+
font-weight: bold;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
.error-stack {
|
|
1012
|
+
margin-top: 4px;
|
|
1013
|
+
font-size: 10px;
|
|
1014
|
+
color: #ccc;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
.debug-actions {
|
|
1018
|
+
padding: 8px;
|
|
1019
|
+
border-top: 1px solid #333;
|
|
1020
|
+
display: flex;
|
|
1021
|
+
gap: 8px;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
.debug-actions button {
|
|
1025
|
+
padding: 4px 8px;
|
|
1026
|
+
background: #444;
|
|
1027
|
+
border: none;
|
|
1028
|
+
color: #fff;
|
|
1029
|
+
cursor: pointer;
|
|
1030
|
+
border-radius: 2px;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
.debug-actions button:hover {
|
|
1034
|
+
background: #555;
|
|
1035
|
+
}
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
## Debugging Best Practices
|
|
1039
|
+
|
|
1040
|
+
### 1. Debugging Checklist
|
|
1041
|
+
|
|
1042
|
+
- [ ] Enable React DevTools
|
|
1043
|
+
- [ ] Use console.log strategically
|
|
1044
|
+
- [ ] Implement error boundaries
|
|
1045
|
+
- [ ] Track component renders
|
|
1046
|
+
- [ ] Monitor hook dependencies
|
|
1047
|
+
- [ ] Debug authentication flow
|
|
1048
|
+
- [ ] Check permission logic
|
|
1049
|
+
- [ ] Monitor API calls
|
|
1050
|
+
- [ ] Track performance metrics
|
|
1051
|
+
- [ ] Use debug panels in development
|
|
1052
|
+
|
|
1053
|
+
### 2. Common Debugging Scenarios
|
|
1054
|
+
|
|
1055
|
+
```typescript
|
|
1056
|
+
// Debugging authentication issues
|
|
1057
|
+
function debugAuthFlow() {
|
|
1058
|
+
const auth = useUnifiedAuth();
|
|
1059
|
+
|
|
1060
|
+
useEffect(() => {
|
|
1061
|
+
console.log('Auth state:', {
|
|
1062
|
+
user: auth.user,
|
|
1063
|
+
loading: auth.loading,
|
|
1064
|
+
error: auth.error,
|
|
1065
|
+
});
|
|
1066
|
+
}, [auth.user, auth.loading, auth.error]);
|
|
1067
|
+
|
|
1068
|
+
// Debug specific auth actions
|
|
1069
|
+
const debugSignIn = async (credentials: any) => {
|
|
1070
|
+
console.log('Attempting sign in with:', credentials);
|
|
1071
|
+
try {
|
|
1072
|
+
const result = await auth.signIn(credentials);
|
|
1073
|
+
console.log('Sign in result:', result);
|
|
1074
|
+
} catch (error) {
|
|
1075
|
+
console.error('Sign in failed:', error);
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// Debugging permission issues
|
|
1081
|
+
function debugPermissions() {
|
|
1082
|
+
const rbac = useRBAC();
|
|
1083
|
+
|
|
1084
|
+
useEffect(() => {
|
|
1085
|
+
console.log('RBAC state:', {
|
|
1086
|
+
permissions: rbac.permissions,
|
|
1087
|
+
roles: rbac.roles,
|
|
1088
|
+
});
|
|
1089
|
+
}, [rbac.permissions, rbac.roles]);
|
|
1090
|
+
|
|
1091
|
+
// Test specific permissions
|
|
1092
|
+
const testPermission = (permission: string) => {
|
|
1093
|
+
const hasPermission = rbac.hasPermission(permission);
|
|
1094
|
+
console.log(`Permission test: ${permission} = ${hasPermission}`);
|
|
1095
|
+
return hasPermission;
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// Debugging API issues
|
|
1100
|
+
function debugAPI() {
|
|
1101
|
+
const { supabase } = useSupabase();
|
|
1102
|
+
|
|
1103
|
+
const debugQuery = async (table: string) => {
|
|
1104
|
+
console.log(`Querying ${table}...`);
|
|
1105
|
+
try {
|
|
1106
|
+
const { data, error } = await supabase.from(table).select('*');
|
|
1107
|
+
console.log(`${table} query result:`, { data, error });
|
|
1108
|
+
return { data, error };
|
|
1109
|
+
} catch (error) {
|
|
1110
|
+
console.error(`${table} query failed:`, error);
|
|
1111
|
+
throw error;
|
|
1112
|
+
}
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
```
|
|
1116
|
+
|
|
1117
|
+
For more information about debugging your application, see the [Common Issues Guide](./common-issues.md) and [Migration Guide](./migration.md).
|