diff --git a/.gitignore b/.gitignore index d0316c9..9880168 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ Thumbs.db .idea/ .vscode/ *.swp +*.bak diff --git a/frontend/index.html b/frontend/index.html index 56591b7..f30ab98 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -5,6 +5,9 @@ AC Tool — Oliver Agency + + +
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5eb0734..e63bfae 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,5 +1,6 @@ import { useEffect } from 'react' import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom' +import ErrorBoundary from './components/ErrorBoundary' import { useMsal } from '@azure/msal-react' import { InteractionStatus } from '@azure/msal-browser' import { Toaster } from 'react-hot-toast' @@ -64,6 +65,7 @@ function AdminRoute({ children }: { children: React.ReactNode }) { export default function App() { return ( + + ) } diff --git a/frontend/src/components/ErrorBoundary.tsx b/frontend/src/components/ErrorBoundary.tsx new file mode 100644 index 0000000..26bb93c --- /dev/null +++ b/frontend/src/components/ErrorBoundary.tsx @@ -0,0 +1,36 @@ +import { Component, type ReactNode } from 'react' + +interface Props { children: ReactNode } +interface State { error: Error | null } + +export default class ErrorBoundary extends Component { + state: State = { error: null } + + static getDerivedStateFromError(error: Error): State { + return { error } + } + + render() { + if (this.state.error) { + return ( +
+
⚠️
+
Something went wrong
+
+ {this.state.error.message} +
+ +
+ ) + } + return this.props.children + } +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 610118e..08bbcdc 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -2,9 +2,13 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import { MsalProvider } from '@azure/msal-react' import { PublicClientApplication } from '@azure/msal-browser' +import { registerAllModules } from 'handsontable/registry' import './index.css' import App from './App' +// Register all Handsontable modules (autocomplete, dropdown, context menu, etc.) +registerAllModules() + // MSAL is configured dynamically from /api/auth/config // We create a placeholder instance here; the real config is loaded in App.tsx const msalInstance = new PublicClientApplication({ diff --git a/frontend/src/pages/SheetPage.tsx b/frontend/src/pages/SheetPage.tsx index 918ea83..c449a1a 100644 --- a/frontend/src/pages/SheetPage.tsx +++ b/frontend/src/pages/SheetPage.tsx @@ -26,14 +26,17 @@ export default function SheetPage() { const [yolo, setYolo] = useState(false) const [history, setHistory] = useState('') const [logs, setLogs] = useState([]) + const [sheetError, setSheetError] = useState(false) const saveTimeoutRef = useRef | null>(null) const sheetMeta = sheets.find(s => s.id === sheetId) + const { loading } = useSheetStore() useEffect(() => { + setSheetError(false) fetchCategories() if (sheetId && activeSheetId !== sheetId) { - loadSheet(sheetId) + loadSheet(sheetId).catch(() => setSheetError(true)) } }, [sheetId]) @@ -142,6 +145,26 @@ export default function SheetPage() { toast.success('Sheet cleared') } + if (sheetError) { + return ( +
+
⚠️
+
Sheet not found
+
+ This sheet may have been deleted or you don't have access. +
+
+ ) + } + + if (loading && deliverables.length === 0) { + return ( +
+
Loading…
+
+ ) + } + return (
{/* Header */} @@ -195,6 +218,8 @@ export default function SheetPage() { licenseKey="non-commercial-and-evaluation" stretchH="last" themeName="ht-theme-main" + autoRowSize={false} + autoColumnSize={false} /> )}