documentation-hub 5.7.2
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/.eslintrc.json +43 -0
- package/.github/workflows/build.yml +64 -0
- package/.github/workflows/ci.yml +39 -0
- package/.vscode/extensions.json +3 -0
- package/Current.md +97 -0
- package/DocHub_Image.png +0 -0
- package/README.md +666 -0
- package/USER_GUIDE.md +1173 -0
- package/Updater.md +311 -0
- package/build/256x256.png +0 -0
- package/build/512x512.png +0 -0
- package/build/app-update.yml +4 -0
- package/build/create-icon.js +208 -0
- package/build/icon.ico +0 -0
- package/build/icon.png +0 -0
- package/build/icon_1024x1024.png +0 -0
- package/dist/assets/Analytics-BpsG9895.js +1 -0
- package/dist/assets/Card-IAZin8kp.js +1 -0
- package/dist/assets/CurrentSession-B-rFkHvf.js +12 -0
- package/dist/assets/Dashboard-C_5gMb0q.js +1 -0
- package/dist/assets/Documents-CqZ25axS.js +1 -0
- package/dist/assets/Input-l89xwXBi.js +1 -0
- package/dist/assets/Reporting-DqdHJY_a.js +1 -0
- package/dist/assets/Search-XNbu5z_3.js +1 -0
- package/dist/assets/SessionManager-lH9hZfzH.js +1 -0
- package/dist/assets/Sessions-ClZOPYNc.js +1 -0
- package/dist/assets/Settings-DUEHGURa.js +11 -0
- package/dist/assets/index-8xUe8ptc.js +24 -0
- package/dist/assets/index-RYyJqF7O.css +1 -0
- package/dist/assets/path-BkOl0AGO.js +1 -0
- package/dist/assets/promises-ID_B9S-h.js +1 -0
- package/dist/assets/urlHelpers-TvgahX0r.js +1 -0
- package/dist/assets/useToast-yRSO1dkm.js +1 -0
- package/dist/assets/vendor-charts-RkGK5ROP.js +36 -0
- package/dist/assets/vendor-db-l0sNRNKZ.js +1 -0
- package/dist/assets/vendor-react-BVZ_anCF.js +4 -0
- package/dist/assets/vendor-search-Dw8P0qyA.js +1 -0
- package/dist/assets/vendor-ui-BU7NfluV.js +53 -0
- package/dist/electron/PowerAutomateApiService-LfW09ZGr.js +147 -0
- package/dist/electron/main-CXkNtyv-.js +19789 -0
- package/dist/electron/main.js +5 -0
- package/dist/electron/preload.js +1 -0
- package/dist/icon.png +0 -0
- package/dist/index.html +27 -0
- package/docs/CODEBASE_ANALYSIS_REPORT.md +309 -0
- package/docs/DEBUG_LOGGING_GUIDE.md +244 -0
- package/docs/README.md +115 -0
- package/docs/TOC_WIRING_GUIDE.md +344 -0
- package/docs/analysis/Bullet_Symbol_Bug_Analysis.md +136 -0
- package/docs/analysis/DOCXMLATER_ANALYSIS_SUMMARY.txt +169 -0
- package/docs/analysis/Document_Processing_Issues_Analysis.md +704 -0
- package/docs/analysis/FIELD_PRESERVATION_ANALYSIS.md +1200 -0
- package/docs/analysis/INDENTATION_PRESERVE_ANALYSIS.md +181 -0
- package/docs/analysis/INDENTATION_PRESERVE_IMPLEMENTATION.md +207 -0
- package/docs/analysis/List_Implementation.md +206 -0
- package/docs/analysis/List_Implementation_Accuracy_Report.md +366 -0
- package/docs/analysis/PROCESSING_OPTIONS_UI_UPDATES.md +220 -0
- package/docs/analysis/RefactorStyles.md +852 -0
- package/docs/analysis/STYLE_PARAMETER_ENHANCEMENT.md +143 -0
- package/docs/analysis/docxmlater-comparison-todo-2025-11-13.md +636 -0
- package/docs/analysis/docxmlater-implementation-analysis-2025-11-13.md +340 -0
- package/docs/analysis/docxmlater-template_ui-integration-analysis.md +263 -0
- package/docs/analysis/github-issues-to-create.md +237 -0
- package/docs/api/API_README.md +538 -0
- package/docs/api/API_REFERENCE.md +751 -0
- package/docs/api/TYPE_DEFINITIONS.md +869 -0
- package/docs/architecture/FONT_EMBEDDING_GUIDE.md +318 -0
- package/docs/architecture/docxmlater-functions-and-structure.md +726 -0
- package/docs/docxmlater-readme.md +1341 -0
- package/docs/fixes/EXECUTION_LOG_TEST_BASE.md +573 -0
- package/docs/fixes/HYPERLINK_TEXT_SANITIZATION.md +253 -0
- package/docs/fixes/README.md +37 -0
- package/docs/github-issues/issue-1-body.md +125 -0
- package/docs/github-issues/issue-10-body.md +850 -0
- package/docs/github-issues/issue-2-body.md +200 -0
- package/docs/github-issues/issue-3-body.md +270 -0
- package/docs/github-issues/issue-4-body.md +169 -0
- package/docs/github-issues/issue-5-body.md +173 -0
- package/docs/github-issues/issue-6-body.md +158 -0
- package/docs/github-issues/issue-7-body.md +171 -0
- package/docs/github-issues/issue-8-body.md +407 -0
- package/docs/github-issues/issue-9-body.md +515 -0
- package/docs/github-issues/issue-tracker.md +274 -0
- package/docs/github-issues/predictive-analysis-2025-10-18.md +2131 -0
- package/docs/implementation/List_Framework_Refactor_Plan.md +336 -0
- package/docs/implementation/PRIMARY_TEXT_COLOR_FEATURE.md +217 -0
- package/docs/implementation/RELEASE_PLAN_v2.1.0.md +362 -0
- package/docs/implementation/RefactorStyles.md +588 -0
- package/docs/implementation/implement-plan.md +489 -0
- package/docs/implementation/missing-helpers-implementation.md +391 -0
- package/docs/implementation/refactor-plan.md +520 -0
- package/docs/implementation/session-implementation-complete.md +233 -0
- package/docs/implementation/session-management-plan.md +250 -0
- package/docs/setup-checklist.md +77 -0
- package/docs/versions/changelog.md +345 -0
- package/electron/customUpdater.ts +656 -0
- package/electron/main.ts +2441 -0
- package/electron/memoryConfig.ts +187 -0
- package/electron/preload.ts +394 -0
- package/electron/proxyConfig.ts +340 -0
- package/electron/services/BackupService.ts +452 -0
- package/electron/services/DictionaryService.ts +402 -0
- package/electron/services/LocalDictionaryLookupService.ts +147 -0
- package/electron/services/PowerAutomateApiService.ts +231 -0
- package/electron/services/SharePointSyncService.ts +474 -0
- package/electron/windowsCertStore.ts +427 -0
- package/electron/zscalerConfig.ts +381 -0
- package/eslint.config.js +92 -0
- package/jest.config.js +52 -0
- package/package.json +214 -0
- package/postcss.config.mjs +6 -0
- package/public/icon.png +0 -0
- package/publish-release.ps1 +5 -0
- package/renovate.json +30 -0
- package/src/App.tsx +216 -0
- package/src/__mocks__/p-limit.js +12 -0
- package/src/__mocks__/styleMock.js +1 -0
- package/src/components/common/BugReportButton.tsx +44 -0
- package/src/components/common/BugReportDialog.tsx +193 -0
- package/src/components/common/Button.tsx +153 -0
- package/src/components/common/Card.tsx +86 -0
- package/src/components/common/ColorPickerDialog.tsx +177 -0
- package/src/components/common/ConfirmDialog.tsx +96 -0
- package/src/components/common/DebugConsole.tsx +275 -0
- package/src/components/common/EmptyState.tsx +183 -0
- package/src/components/common/ErrorBoundary.tsx +98 -0
- package/src/components/common/ErrorDetailsDialog.tsx +153 -0
- package/src/components/common/ErrorFallback.tsx +218 -0
- package/src/components/common/Input.tsx +109 -0
- package/src/components/common/Skeleton.tsx +184 -0
- package/src/components/common/SplashScreen.tsx +81 -0
- package/src/components/common/Toast.tsx +155 -0
- package/src/components/common/Tooltip.tsx +79 -0
- package/src/components/common/UpdateNotification.tsx +320 -0
- package/src/components/comparison/ComparisonWindow.tsx +374 -0
- package/src/components/comparison/SideBySideDiff.tsx +486 -0
- package/src/components/comparison/index.ts +8 -0
- package/src/components/document/DocumentUploader.tsx +288 -0
- package/src/components/document/HyperlinkPreview.tsx +430 -0
- package/src/components/document/HyperlinkService.md +1484 -0
- package/src/components/document/Hyperlink_Technical_Documentation.md +496 -0
- package/src/components/document/InlineChangesView.tsx +707 -0
- package/src/components/document/ProcessingProgress.tsx +303 -0
- package/src/components/document/ProcessingResults.tsx +256 -0
- package/src/components/document/TrackedChangesDetail.tsx +530 -0
- package/src/components/document/TrackedChangesPanel.tsx +546 -0
- package/src/components/document/VirtualDocumentList.tsx +240 -0
- package/src/components/editor/DocumentEditor.tsx +723 -0
- package/src/components/editor/DocumentEditorModal.tsx +640 -0
- package/src/components/editor/EditorQuickActions.tsx +502 -0
- package/src/components/editor/EditorToolbar.tsx +312 -0
- package/src/components/editor/TableEditor.tsx +926 -0
- package/src/components/editor/index.ts +18 -0
- package/src/components/layout/Header.tsx +190 -0
- package/src/components/layout/Sidebar.tsx +313 -0
- package/src/components/layout/TitleBar.tsx +190 -0
- package/src/components/navigation/CommandPalette.tsx +233 -0
- package/src/components/navigation/KeyboardShortcutsModal.tsx +173 -0
- package/src/components/sessions/ChangeItem.tsx +408 -0
- package/src/components/sessions/ChangeViewer.tsx +1155 -0
- package/src/components/sessions/DocumentComparisonModal.tsx +314 -0
- package/src/components/sessions/ProcessingOptions.tsx +297 -0
- package/src/components/sessions/ReplacementsTab.tsx +438 -0
- package/src/components/sessions/RevisionHandlingOptions.tsx +87 -0
- package/src/components/sessions/SessionManager.tsx +188 -0
- package/src/components/sessions/StylesEditor.tsx +1335 -0
- package/src/components/sessions/TabContainer.tsx +151 -0
- package/src/components/sessions/VirtualSessionList.tsx +157 -0
- package/src/components/sessions/sessionToProcessorManager.tsx +420 -0
- package/src/components/settings/CertificateManager.tsx +410 -0
- package/src/components/settings/SegmentedControl.tsx +88 -0
- package/src/components/settings/SettingRow.tsx +52 -0
- package/src/contexts/GlobalStatsContext.tsx +396 -0
- package/src/contexts/SessionContext.tsx +2129 -0
- package/src/contexts/ThemeContext.tsx +428 -0
- package/src/contexts/UserSettingsContext.tsx +290 -0
- package/src/contexts/__tests__/GlobalStatsContext.test.tsx +390 -0
- package/src/global.d.ts +273 -0
- package/src/hooks/useDocumentQueue.tsx +210 -0
- package/src/hooks/useToast.tsx +55 -0
- package/src/main.tsx +10 -0
- package/src/pages/Analytics.tsx +386 -0
- package/src/pages/CurrentSession.tsx +1174 -0
- package/src/pages/Dashboard.tsx +319 -0
- package/src/pages/Documents.tsx +317 -0
- package/src/pages/Projects.tsx +250 -0
- package/src/pages/Reporting.tsx +386 -0
- package/src/pages/Search.tsx +349 -0
- package/src/pages/Sessions.tsx +285 -0
- package/src/pages/Settings.tsx +2662 -0
- package/src/services/HyperlinkService.ts +1085 -0
- package/src/services/document/DocXMLaterProcessor.ts +617 -0
- package/src/services/document/DocumentProcessingComparison.ts +856 -0
- package/src/services/document/DocumentSnapshotService.ts +575 -0
- package/src/services/document/WordDocumentProcessor.ts +10509 -0
- package/src/services/document/__tests__/DocXMLaterProcessor.hyperlinks.test.md +311 -0
- package/src/services/document/__tests__/WordDocumentProcessor.integration.test.ts +515 -0
- package/src/services/document/__tests__/WordDocumentProcessor.test.ts +812 -0
- package/src/services/document/blanklines/BlankLineManager.ts +658 -0
- package/src/services/document/blanklines/__tests__/paragraphChecks.test.ts +281 -0
- package/src/services/document/blanklines/helpers/blankLineInsertion.ts +87 -0
- package/src/services/document/blanklines/helpers/blankLineSnapshot.ts +251 -0
- package/src/services/document/blanklines/helpers/clearCustom.ts +121 -0
- package/src/services/document/blanklines/helpers/contextChecks.ts +117 -0
- package/src/services/document/blanklines/helpers/imageChecks.ts +51 -0
- package/src/services/document/blanklines/helpers/paragraphChecks.ts +236 -0
- package/src/services/document/blanklines/helpers/removeBlanksBetweenListItems.ts +91 -0
- package/src/services/document/blanklines/helpers/removeTrailingBlanks.ts +35 -0
- package/src/services/document/blanklines/helpers/tableGuards.ts +21 -0
- package/src/services/document/blanklines/index.ts +67 -0
- package/src/services/document/blanklines/rules/additionRules.ts +337 -0
- package/src/services/document/blanklines/rules/indentationRules.ts +317 -0
- package/src/services/document/blanklines/rules/removalRules.ts +362 -0
- package/src/services/document/blanklines/rules/ruleTypes.ts +92 -0
- package/src/services/document/blanklines/types.ts +29 -0
- package/src/services/document/helpers/ImageBorderCropper.ts +377 -0
- package/src/services/document/helpers/__tests__/whitespace.test.ts +272 -0
- package/src/services/document/helpers/whitespace.ts +117 -0
- package/src/services/document/list/ListNormalizer.ts +947 -0
- package/src/services/document/list/index.ts +45 -0
- package/src/services/document/list/list-detection.ts +275 -0
- package/src/services/document/list/list-types.ts +162 -0
- package/src/services/document/processors/HyperlinkProcessor.ts +370 -0
- package/src/services/document/processors/ListProcessor.ts +257 -0
- package/src/services/document/processors/StructureProcessor.ts +176 -0
- package/src/services/document/processors/StyleProcessor.ts +389 -0
- package/src/services/document/processors/TableProcessor.ts +2238 -0
- package/src/services/document/processors/__tests__/HyperlinkProcessor.test.ts +314 -0
- package/src/services/document/processors/__tests__/ListProcessor.test.ts +291 -0
- package/src/services/document/processors/__tests__/StructureProcessor.test.ts +257 -0
- package/src/services/document/processors/__tests__/TableProcessor.hlp-tips-bullets.test.ts +459 -0
- package/src/services/document/processors/__tests__/TableProcessor.test.ts +1604 -0
- package/src/services/document/processors/index.ts +28 -0
- package/src/services/document/types/docx-processing.ts +310 -0
- package/src/services/editor/EditorActionHandlers.ts +901 -0
- package/src/services/editor/index.ts +13 -0
- package/src/setupTests.ts +47 -0
- package/src/styles/global.css +782 -0
- package/src/types/backup.ts +132 -0
- package/src/types/dictionary.ts +125 -0
- package/src/types/document-processing.ts +331 -0
- package/src/types/docxmlater-augments.d.ts +142 -0
- package/src/types/editor.ts +280 -0
- package/src/types/electron.ts +340 -0
- package/src/types/globalStats.ts +155 -0
- package/src/types/hyperlink.ts +471 -0
- package/src/types/operations.ts +354 -0
- package/src/types/session.ts +427 -0
- package/src/types/settings.ts +112 -0
- package/src/utils/MemoryMonitor.ts +248 -0
- package/src/utils/cn.ts +6 -0
- package/src/utils/colorConvert.ts +306 -0
- package/src/utils/diffUtils.ts +347 -0
- package/src/utils/documentUtils.ts +202 -0
- package/src/utils/electronGuard.ts +62 -0
- package/src/utils/indexedDB.ts +915 -0
- package/src/utils/logger.ts +717 -0
- package/src/utils/pathSecurity.ts +232 -0
- package/src/utils/pathValidator.ts +236 -0
- package/src/utils/processingTimeEstimator.ts +153 -0
- package/src/utils/safeJsonParse.ts +62 -0
- package/src/utils/textSanitizer.ts +162 -0
- package/src/utils/urlHelpers.ts +304 -0
- package/src/utils/urlPatterns.ts +198 -0
- package/src/utils/urlSanitizer.ts +152 -0
- package/src/vite-env.d.ts +11 -0
- package/tsconfig.electron.json +19 -0
- package/tsconfig.json +36 -0
- package/tsconfig.node.json +12 -0
- package/typedoc.json +45 -0
- package/vite.config.ts +152 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as Dialog from '@radix-ui/react-dialog';
|
|
3
|
+
import { Button } from './Button';
|
|
4
|
+
import { X } from 'lucide-react';
|
|
5
|
+
import { cn } from '@/utils/cn';
|
|
6
|
+
import logger from '@/utils/logger';
|
|
7
|
+
|
|
8
|
+
export interface ConfirmDialogProps {
|
|
9
|
+
open: boolean;
|
|
10
|
+
onOpenChange: (open: boolean) => void;
|
|
11
|
+
title: string;
|
|
12
|
+
message: string;
|
|
13
|
+
confirmText?: string;
|
|
14
|
+
cancelText?: string;
|
|
15
|
+
variant?: 'default' | 'destructive';
|
|
16
|
+
onConfirm: () => void | Promise<void>;
|
|
17
|
+
loading?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function ConfirmDialog({
|
|
21
|
+
open,
|
|
22
|
+
onOpenChange,
|
|
23
|
+
title,
|
|
24
|
+
message,
|
|
25
|
+
confirmText = 'Confirm',
|
|
26
|
+
cancelText = 'Cancel',
|
|
27
|
+
variant = 'default',
|
|
28
|
+
onConfirm,
|
|
29
|
+
loading = false,
|
|
30
|
+
}: ConfirmDialogProps) {
|
|
31
|
+
const [isProcessing, setIsProcessing] = React.useState(false);
|
|
32
|
+
|
|
33
|
+
const handleConfirm = async () => {
|
|
34
|
+
setIsProcessing(true);
|
|
35
|
+
try {
|
|
36
|
+
await onConfirm();
|
|
37
|
+
onOpenChange(false);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
logger.error('Confirmation action failed:', error);
|
|
40
|
+
} finally {
|
|
41
|
+
setIsProcessing(false);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Dialog.Root open={open} onOpenChange={onOpenChange}>
|
|
47
|
+
<Dialog.Portal>
|
|
48
|
+
<Dialog.Overlay className="fixed inset-0 bg-black/50 backdrop-blur-xs data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 z-50" />
|
|
49
|
+
<Dialog.Content
|
|
50
|
+
className={cn(
|
|
51
|
+
'fixed left-[50%] top-[50%] z-50 translate-x-[-50%] translate-y-[-50%]',
|
|
52
|
+
'w-full max-w-lg rounded-lg border border-border bg-card p-6 shadow-lg',
|
|
53
|
+
'data-[state=open]:animate-in data-[state=closed]:animate-out',
|
|
54
|
+
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
|
55
|
+
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
|
56
|
+
'data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%]',
|
|
57
|
+
'data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]'
|
|
58
|
+
)}
|
|
59
|
+
>
|
|
60
|
+
{/* Header */}
|
|
61
|
+
<div className="flex items-start justify-between mb-4">
|
|
62
|
+
<Dialog.Title className="text-lg font-semibold text-foreground">{title}</Dialog.Title>
|
|
63
|
+
<Dialog.Close className="rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none">
|
|
64
|
+
<X className="h-4 w-4" />
|
|
65
|
+
<span className="sr-only">Close</span>
|
|
66
|
+
</Dialog.Close>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
{/* Message */}
|
|
70
|
+
<Dialog.Description className="text-sm text-muted-foreground mb-6">
|
|
71
|
+
{message}
|
|
72
|
+
</Dialog.Description>
|
|
73
|
+
|
|
74
|
+
{/* Actions */}
|
|
75
|
+
<div className="flex justify-end gap-3">
|
|
76
|
+
<Button
|
|
77
|
+
variant="outline"
|
|
78
|
+
onClick={() => onOpenChange(false)}
|
|
79
|
+
disabled={isProcessing || loading}
|
|
80
|
+
>
|
|
81
|
+
{cancelText}
|
|
82
|
+
</Button>
|
|
83
|
+
<Button
|
|
84
|
+
variant={variant === 'destructive' ? 'destructive' : 'default'}
|
|
85
|
+
onClick={handleConfirm}
|
|
86
|
+
loading={isProcessing || loading}
|
|
87
|
+
disabled={isProcessing || loading}
|
|
88
|
+
>
|
|
89
|
+
{confirmText}
|
|
90
|
+
</Button>
|
|
91
|
+
</div>
|
|
92
|
+
</Dialog.Content>
|
|
93
|
+
</Dialog.Portal>
|
|
94
|
+
</Dialog.Root>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
3
|
+
import { X, Terminal, AlertTriangle, Network, Shield, Download, Trash2, Copy } from 'lucide-react';
|
|
4
|
+
import { cn } from '@/utils/cn';
|
|
5
|
+
|
|
6
|
+
interface LogEntry {
|
|
7
|
+
id: string;
|
|
8
|
+
timestamp: string;
|
|
9
|
+
type: 'network' | 'cert-error' | 'network-error' | 'tls-error' | 'info' | 'warning';
|
|
10
|
+
message: string;
|
|
11
|
+
details?: any;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function DebugConsole() {
|
|
15
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
16
|
+
const [logs, setLogs] = useState<LogEntry[]>([]);
|
|
17
|
+
const [filter, setFilter] = useState<'all' | 'network' | 'errors'>('all');
|
|
18
|
+
const scrollRef = useRef<HTMLDivElement>(null);
|
|
19
|
+
const [autoScroll, setAutoScroll] = useState(true);
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
// Listen for debug mode activation (triggered by 5 clicks on logo)
|
|
23
|
+
const handleKeyPress = (e: KeyboardEvent) => {
|
|
24
|
+
// Ctrl+Shift+D to toggle debug console
|
|
25
|
+
if (e.ctrlKey && e.shiftKey && e.key === 'D') {
|
|
26
|
+
setIsVisible(!isVisible);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
window.addEventListener('keydown', handleKeyPress);
|
|
31
|
+
|
|
32
|
+
// Listen for network requests
|
|
33
|
+
const unsubNetworkRequest = window.electronAPI?.onDebugNetworkRequest?.((data: any) => {
|
|
34
|
+
addLog('network', `${data.method} ${data.url}`, data);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Listen for certificate errors
|
|
38
|
+
const unsubCertError = window.electronAPI?.onDebugCertError?.((data: any) => {
|
|
39
|
+
addLog('cert-error', `Certificate Error: ${data.url}`, data);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Listen for network errors
|
|
43
|
+
const unsubNetworkError = window.electronAPI?.onDebugNetworkError?.((data: any) => {
|
|
44
|
+
addLog('network-error', `Network Error: ${data.error}`, data);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Listen for TLS errors
|
|
48
|
+
const unsubTLSError = window.electronAPI?.onDebugTLSError?.((data: any) => {
|
|
49
|
+
addLog('tls-error', `TLS Error: ${data.message}`, data);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Listen for update status
|
|
53
|
+
const unsubUpdateStatus = window.electronAPI?.onUpdateStatus?.((data: any) => {
|
|
54
|
+
if (data.message?.includes('PowerShell') || data.message?.includes('Mutual TLS')) {
|
|
55
|
+
addLog('info', `Update: ${data.message}`, data);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return () => {
|
|
60
|
+
window.removeEventListener('keydown', handleKeyPress);
|
|
61
|
+
unsubNetworkRequest?.();
|
|
62
|
+
unsubCertError?.();
|
|
63
|
+
unsubNetworkError?.();
|
|
64
|
+
unsubTLSError?.();
|
|
65
|
+
unsubUpdateStatus?.();
|
|
66
|
+
};
|
|
67
|
+
}, [isVisible]);
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
// Auto-scroll to bottom when new logs are added
|
|
71
|
+
if (autoScroll && scrollRef.current) {
|
|
72
|
+
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
|
73
|
+
}
|
|
74
|
+
}, [logs, autoScroll]);
|
|
75
|
+
|
|
76
|
+
const addLog = (type: LogEntry['type'], message: string, details?: any) => {
|
|
77
|
+
const newLog: LogEntry = {
|
|
78
|
+
id: `${Date.now()}-${Math.random()}`,
|
|
79
|
+
timestamp: new Date().toISOString(),
|
|
80
|
+
type,
|
|
81
|
+
message,
|
|
82
|
+
details,
|
|
83
|
+
};
|
|
84
|
+
setLogs((prev) => [...prev.slice(-200), newLog]); // Keep last 200 logs
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const clearLogs = () => {
|
|
88
|
+
setLogs([]);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const exportLogs = () => {
|
|
92
|
+
const logsJson = JSON.stringify(logs, null, 2);
|
|
93
|
+
const blob = new Blob([logsJson], { type: 'application/json' });
|
|
94
|
+
const url = URL.createObjectURL(blob);
|
|
95
|
+
const a = document.createElement('a');
|
|
96
|
+
a.href = url;
|
|
97
|
+
a.download = `debug-logs-${Date.now()}.json`;
|
|
98
|
+
a.click();
|
|
99
|
+
URL.revokeObjectURL(url);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const copyLogs = () => {
|
|
103
|
+
const logsText = logs
|
|
104
|
+
.map((log) => `[${log.timestamp}] [${log.type.toUpperCase()}] ${log.message}`)
|
|
105
|
+
.join('\n');
|
|
106
|
+
navigator.clipboard.writeText(logsText);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const filteredLogs = logs.filter((log) => {
|
|
110
|
+
if (filter === 'all') return true;
|
|
111
|
+
if (filter === 'network') return log.type === 'network';
|
|
112
|
+
if (filter === 'errors') return log.type.includes('error');
|
|
113
|
+
return true;
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const getLogIcon = (type: LogEntry['type']) => {
|
|
117
|
+
switch (type) {
|
|
118
|
+
case 'network':
|
|
119
|
+
return <Network className="w-3 h-3" />;
|
|
120
|
+
case 'cert-error':
|
|
121
|
+
return <Shield className="w-3 h-3 text-red-500" />;
|
|
122
|
+
case 'network-error':
|
|
123
|
+
case 'tls-error':
|
|
124
|
+
return <AlertTriangle className="w-3 h-3 text-yellow-500" />;
|
|
125
|
+
default:
|
|
126
|
+
return <Terminal className="w-3 h-3" />;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const getLogColor = (type: LogEntry['type']) => {
|
|
131
|
+
switch (type) {
|
|
132
|
+
case 'cert-error':
|
|
133
|
+
case 'tls-error':
|
|
134
|
+
return 'text-red-400';
|
|
135
|
+
case 'network-error':
|
|
136
|
+
return 'text-yellow-400';
|
|
137
|
+
case 'network':
|
|
138
|
+
return 'text-blue-400';
|
|
139
|
+
default:
|
|
140
|
+
return 'text-muted-foreground';
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<AnimatePresence>
|
|
146
|
+
{isVisible && (
|
|
147
|
+
<motion.div
|
|
148
|
+
initial={{ opacity: 0, y: 100 }}
|
|
149
|
+
animate={{ opacity: 1, y: 0 }}
|
|
150
|
+
exit={{ opacity: 0, y: 100 }}
|
|
151
|
+
className="fixed bottom-4 right-4 z-[999] w-[600px] h-[400px] bg-background/95 backdrop-blur-xl border border-border rounded-lg shadow-2xl flex flex-col"
|
|
152
|
+
>
|
|
153
|
+
{/* Header */}
|
|
154
|
+
<div className="flex items-center justify-between px-4 py-2 border-b border-border">
|
|
155
|
+
<div className="flex items-center gap-2">
|
|
156
|
+
<Terminal className="w-4 h-4 text-primary" />
|
|
157
|
+
<span className="text-sm font-semibold">Debug Console</span>
|
|
158
|
+
<span className="text-xs text-muted-foreground">({filteredLogs.length} logs)</span>
|
|
159
|
+
</div>
|
|
160
|
+
<div className="flex items-center gap-1">
|
|
161
|
+
{/* Filter buttons */}
|
|
162
|
+
<button
|
|
163
|
+
onClick={() => setFilter('all')}
|
|
164
|
+
className={cn(
|
|
165
|
+
'px-2 py-1 text-xs rounded transition-colors',
|
|
166
|
+
filter === 'all' ? 'bg-primary text-primary-foreground' : 'hover:bg-muted'
|
|
167
|
+
)}
|
|
168
|
+
>
|
|
169
|
+
All
|
|
170
|
+
</button>
|
|
171
|
+
<button
|
|
172
|
+
onClick={() => setFilter('network')}
|
|
173
|
+
className={cn(
|
|
174
|
+
'px-2 py-1 text-xs rounded transition-colors',
|
|
175
|
+
filter === 'network' ? 'bg-primary text-primary-foreground' : 'hover:bg-muted'
|
|
176
|
+
)}
|
|
177
|
+
>
|
|
178
|
+
Network
|
|
179
|
+
</button>
|
|
180
|
+
<button
|
|
181
|
+
onClick={() => setFilter('errors')}
|
|
182
|
+
className={cn(
|
|
183
|
+
'px-2 py-1 text-xs rounded transition-colors',
|
|
184
|
+
filter === 'errors' ? 'bg-primary text-primary-foreground' : 'hover:bg-muted'
|
|
185
|
+
)}
|
|
186
|
+
>
|
|
187
|
+
Errors
|
|
188
|
+
</button>
|
|
189
|
+
<div className="w-px h-4 bg-border mx-1" />
|
|
190
|
+
<button
|
|
191
|
+
onClick={copyLogs}
|
|
192
|
+
className="p-1 hover:bg-muted rounded transition-colors"
|
|
193
|
+
title="Copy logs"
|
|
194
|
+
>
|
|
195
|
+
<Copy className="w-3.5 h-3.5" />
|
|
196
|
+
</button>
|
|
197
|
+
<button
|
|
198
|
+
onClick={exportLogs}
|
|
199
|
+
className="p-1 hover:bg-muted rounded transition-colors"
|
|
200
|
+
title="Export logs"
|
|
201
|
+
>
|
|
202
|
+
<Download className="w-3.5 h-3.5" />
|
|
203
|
+
</button>
|
|
204
|
+
<button
|
|
205
|
+
onClick={clearLogs}
|
|
206
|
+
className="p-1 hover:bg-muted rounded transition-colors"
|
|
207
|
+
title="Clear logs"
|
|
208
|
+
>
|
|
209
|
+
<Trash2 className="w-3.5 h-3.5" />
|
|
210
|
+
</button>
|
|
211
|
+
<button
|
|
212
|
+
onClick={() => setIsVisible(false)}
|
|
213
|
+
className="p-1 hover:bg-muted rounded transition-colors ml-2"
|
|
214
|
+
>
|
|
215
|
+
<X className="w-4 h-4" />
|
|
216
|
+
</button>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
{/* Log content */}
|
|
221
|
+
<div
|
|
222
|
+
ref={scrollRef}
|
|
223
|
+
className="flex-1 overflow-y-auto p-2 font-mono text-[11px] space-y-0.5"
|
|
224
|
+
onScroll={(e) => {
|
|
225
|
+
const target = e.target as HTMLDivElement;
|
|
226
|
+
const isAtBottom = target.scrollHeight - target.scrollTop === target.clientHeight;
|
|
227
|
+
setAutoScroll(isAtBottom);
|
|
228
|
+
}}
|
|
229
|
+
>
|
|
230
|
+
{filteredLogs.length === 0 ? (
|
|
231
|
+
<div className="text-center text-muted-foreground py-8">
|
|
232
|
+
No logs to display. Press Ctrl+Shift+D to toggle this console.
|
|
233
|
+
</div>
|
|
234
|
+
) : (
|
|
235
|
+
filteredLogs.map((log) => (
|
|
236
|
+
<div
|
|
237
|
+
key={log.id}
|
|
238
|
+
className="flex items-start gap-2 hover:bg-muted/30 px-2 py-0.5 rounded"
|
|
239
|
+
>
|
|
240
|
+
<span className="flex-shrink-0 mt-0.5">{getLogIcon(log.type)}</span>
|
|
241
|
+
<span className="text-muted-foreground flex-shrink-0">
|
|
242
|
+
{log.timestamp.split('T')[1].split('.')[0]}
|
|
243
|
+
</span>
|
|
244
|
+
<span className={cn('flex-1 break-all', getLogColor(log.type))}>
|
|
245
|
+
{log.message}
|
|
246
|
+
</span>
|
|
247
|
+
</div>
|
|
248
|
+
))
|
|
249
|
+
)}
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
{/* Footer */}
|
|
253
|
+
<div className="px-4 py-2 border-t border-border flex items-center justify-between">
|
|
254
|
+
<div className="flex items-center gap-2">
|
|
255
|
+
<label className="flex items-center gap-1 text-xs">
|
|
256
|
+
<input
|
|
257
|
+
type="checkbox"
|
|
258
|
+
checked={autoScroll}
|
|
259
|
+
onChange={(e) => setAutoScroll(e.target.checked)}
|
|
260
|
+
className="w-3 h-3"
|
|
261
|
+
/>
|
|
262
|
+
Auto-scroll
|
|
263
|
+
</label>
|
|
264
|
+
</div>
|
|
265
|
+
<div className="text-xs text-muted-foreground">
|
|
266
|
+
Press <kbd className="px-1 py-0.5 bg-muted rounded text-[10px]">Ctrl</kbd>+
|
|
267
|
+
<kbd className="px-1 py-0.5 bg-muted rounded text-[10px]">Shift</kbd>+
|
|
268
|
+
<kbd className="px-1 py-0.5 bg-muted rounded text-[10px]">D</kbd> to toggle
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
271
|
+
</motion.div>
|
|
272
|
+
)}
|
|
273
|
+
</AnimatePresence>
|
|
274
|
+
);
|
|
275
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { cn } from '@/utils/cn';
|
|
2
|
+
import { motion } from 'framer-motion';
|
|
3
|
+
import type { LucideIcon } from 'lucide-react';
|
|
4
|
+
import { Button } from './Button';
|
|
5
|
+
|
|
6
|
+
interface EmptyStateAction {
|
|
7
|
+
label: string;
|
|
8
|
+
onClick: () => void;
|
|
9
|
+
variant?: 'default' | 'outline' | 'ghost';
|
|
10
|
+
icon?: LucideIcon;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface EmptyStateProps {
|
|
14
|
+
icon?: LucideIcon;
|
|
15
|
+
title: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
primaryAction?: EmptyStateAction;
|
|
18
|
+
secondaryAction?: EmptyStateAction;
|
|
19
|
+
className?: string;
|
|
20
|
+
size?: 'sm' | 'md' | 'lg';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function EmptyState({
|
|
24
|
+
icon: Icon,
|
|
25
|
+
title,
|
|
26
|
+
description,
|
|
27
|
+
primaryAction,
|
|
28
|
+
secondaryAction,
|
|
29
|
+
className,
|
|
30
|
+
size = 'md',
|
|
31
|
+
}: EmptyStateProps) {
|
|
32
|
+
const sizeStyles = {
|
|
33
|
+
sm: {
|
|
34
|
+
container: 'py-8 px-4',
|
|
35
|
+
icon: 'w-10 h-10',
|
|
36
|
+
iconWrapper: 'w-16 h-16',
|
|
37
|
+
title: 'text-base',
|
|
38
|
+
description: 'text-sm',
|
|
39
|
+
},
|
|
40
|
+
md: {
|
|
41
|
+
container: 'py-12 px-6',
|
|
42
|
+
icon: 'w-12 h-12',
|
|
43
|
+
iconWrapper: 'w-20 h-20',
|
|
44
|
+
title: 'text-lg',
|
|
45
|
+
description: 'text-sm',
|
|
46
|
+
},
|
|
47
|
+
lg: {
|
|
48
|
+
container: 'py-16 px-8',
|
|
49
|
+
icon: 'w-14 h-14',
|
|
50
|
+
iconWrapper: 'w-24 h-24',
|
|
51
|
+
title: 'text-xl',
|
|
52
|
+
description: 'text-base',
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const styles = sizeStyles[size];
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<motion.div
|
|
60
|
+
initial={{ opacity: 0, y: 20 }}
|
|
61
|
+
animate={{ opacity: 1, y: 0 }}
|
|
62
|
+
transition={{ duration: 0.3 }}
|
|
63
|
+
className={cn(
|
|
64
|
+
'flex flex-col items-center justify-center text-center',
|
|
65
|
+
styles.container,
|
|
66
|
+
className
|
|
67
|
+
)}
|
|
68
|
+
>
|
|
69
|
+
{Icon && (
|
|
70
|
+
<motion.div
|
|
71
|
+
initial={{ scale: 0.8 }}
|
|
72
|
+
animate={{ scale: 1 }}
|
|
73
|
+
transition={{ delay: 0.1, type: 'spring', stiffness: 200 }}
|
|
74
|
+
className={cn(
|
|
75
|
+
'rounded-full bg-muted/50 flex items-center justify-center mb-4',
|
|
76
|
+
styles.iconWrapper
|
|
77
|
+
)}
|
|
78
|
+
>
|
|
79
|
+
<Icon className={cn('text-muted-foreground', styles.icon)} />
|
|
80
|
+
</motion.div>
|
|
81
|
+
)}
|
|
82
|
+
|
|
83
|
+
<motion.h3
|
|
84
|
+
initial={{ opacity: 0 }}
|
|
85
|
+
animate={{ opacity: 1 }}
|
|
86
|
+
transition={{ delay: 0.15 }}
|
|
87
|
+
className={cn('font-semibold text-foreground mb-1', styles.title)}
|
|
88
|
+
>
|
|
89
|
+
{title}
|
|
90
|
+
</motion.h3>
|
|
91
|
+
|
|
92
|
+
{description && (
|
|
93
|
+
<motion.p
|
|
94
|
+
initial={{ opacity: 0 }}
|
|
95
|
+
animate={{ opacity: 1 }}
|
|
96
|
+
transition={{ delay: 0.2 }}
|
|
97
|
+
className={cn(
|
|
98
|
+
'text-muted-foreground max-w-sm mb-6',
|
|
99
|
+
styles.description
|
|
100
|
+
)}
|
|
101
|
+
>
|
|
102
|
+
{description}
|
|
103
|
+
</motion.p>
|
|
104
|
+
)}
|
|
105
|
+
|
|
106
|
+
{(primaryAction || secondaryAction) && (
|
|
107
|
+
<motion.div
|
|
108
|
+
initial={{ opacity: 0, y: 10 }}
|
|
109
|
+
animate={{ opacity: 1, y: 0 }}
|
|
110
|
+
transition={{ delay: 0.25 }}
|
|
111
|
+
className="flex items-center gap-3"
|
|
112
|
+
>
|
|
113
|
+
{primaryAction && (
|
|
114
|
+
<Button
|
|
115
|
+
onClick={primaryAction.onClick}
|
|
116
|
+
variant={primaryAction.variant || 'default'}
|
|
117
|
+
icon={primaryAction.icon && <primaryAction.icon className="w-4 h-4" />}
|
|
118
|
+
>
|
|
119
|
+
{primaryAction.label}
|
|
120
|
+
</Button>
|
|
121
|
+
)}
|
|
122
|
+
{secondaryAction && (
|
|
123
|
+
<Button
|
|
124
|
+
onClick={secondaryAction.onClick}
|
|
125
|
+
variant={secondaryAction.variant || 'ghost'}
|
|
126
|
+
icon={secondaryAction.icon && <secondaryAction.icon className="w-4 h-4" />}
|
|
127
|
+
>
|
|
128
|
+
{secondaryAction.label}
|
|
129
|
+
</Button>
|
|
130
|
+
)}
|
|
131
|
+
</motion.div>
|
|
132
|
+
)}
|
|
133
|
+
</motion.div>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Preset empty states for common use cases
|
|
138
|
+
export function NoSessionsEmptyState({ onCreateSession }: { onCreateSession: () => void }) {
|
|
139
|
+
return (
|
|
140
|
+
<EmptyState
|
|
141
|
+
title="No sessions yet"
|
|
142
|
+
description="Create your first session to start processing documents"
|
|
143
|
+
primaryAction={{
|
|
144
|
+
label: 'Create Session',
|
|
145
|
+
onClick: onCreateSession,
|
|
146
|
+
}}
|
|
147
|
+
size="lg"
|
|
148
|
+
/>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function NoDocumentsEmptyState({ onAddDocuments }: { onAddDocuments: () => void }) {
|
|
153
|
+
return (
|
|
154
|
+
<EmptyState
|
|
155
|
+
title="No documents"
|
|
156
|
+
description="Drop files here or click to add documents to this session"
|
|
157
|
+
primaryAction={{
|
|
158
|
+
label: 'Add Documents',
|
|
159
|
+
onClick: onAddDocuments,
|
|
160
|
+
}}
|
|
161
|
+
/>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function NoSearchResultsEmptyState({ query }: { query: string }) {
|
|
166
|
+
return (
|
|
167
|
+
<EmptyState
|
|
168
|
+
title="No results found"
|
|
169
|
+
description={`No documents or sessions match "${query}". Try a different search term.`}
|
|
170
|
+
size="sm"
|
|
171
|
+
/>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function NoChangesEmptyState() {
|
|
176
|
+
return (
|
|
177
|
+
<EmptyState
|
|
178
|
+
title="No changes detected"
|
|
179
|
+
description="Process a document to see tracked changes here"
|
|
180
|
+
size="sm"
|
|
181
|
+
/>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Error Boundary Component
|
|
3
|
+
*
|
|
4
|
+
* Catches JavaScript errors anywhere in the component tree,
|
|
5
|
+
* logs error information, and displays a fallback UI instead
|
|
6
|
+
* of crashing the entire application.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* ```tsx
|
|
10
|
+
* <ErrorBoundary>
|
|
11
|
+
* <YourComponent />
|
|
12
|
+
* </ErrorBoundary>
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import React, { Component, ErrorInfo, ReactNode } from 'react';
|
|
17
|
+
import { logger } from '@/utils/logger';
|
|
18
|
+
import { ErrorFallback } from './ErrorFallback';
|
|
19
|
+
|
|
20
|
+
interface Props {
|
|
21
|
+
children: ReactNode;
|
|
22
|
+
fallback?: ReactNode;
|
|
23
|
+
onError?: (error: Error, errorInfo: ErrorInfo) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface State {
|
|
27
|
+
hasError: boolean;
|
|
28
|
+
error: Error | null;
|
|
29
|
+
errorInfo: ErrorInfo | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class ErrorBoundary extends Component<Props, State> {
|
|
33
|
+
constructor(props: Props) {
|
|
34
|
+
super(props);
|
|
35
|
+
this.state = {
|
|
36
|
+
hasError: false,
|
|
37
|
+
error: null,
|
|
38
|
+
errorInfo: null,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static getDerivedStateFromError(error: Error): Partial<State> {
|
|
43
|
+
// Update state so the next render shows the fallback UI
|
|
44
|
+
return {
|
|
45
|
+
hasError: true,
|
|
46
|
+
error,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
|
|
51
|
+
// Log error to console and any error reporting service
|
|
52
|
+
logger.error('React Error Boundary caught an error:', {
|
|
53
|
+
error: error.toString(),
|
|
54
|
+
componentStack: errorInfo.componentStack,
|
|
55
|
+
errorInfo,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Update state with error info for display
|
|
59
|
+
this.setState({
|
|
60
|
+
errorInfo,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Call custom error handler if provided
|
|
64
|
+
if (this.props.onError) {
|
|
65
|
+
this.props.onError(error, errorInfo);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
handleReset = (): void => {
|
|
70
|
+
this.setState({
|
|
71
|
+
hasError: false,
|
|
72
|
+
error: null,
|
|
73
|
+
errorInfo: null,
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
render(): ReactNode {
|
|
78
|
+
if (this.state.hasError) {
|
|
79
|
+
// Custom fallback UI if provided
|
|
80
|
+
if (this.props.fallback) {
|
|
81
|
+
return this.props.fallback;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Default fallback UI
|
|
85
|
+
return (
|
|
86
|
+
<ErrorFallback
|
|
87
|
+
error={this.state.error}
|
|
88
|
+
errorInfo={this.state.errorInfo}
|
|
89
|
+
onReset={this.handleReset}
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return this.props.children;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export default ErrorBoundary;
|