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 (
+
+ )
+ }
+
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}
/>
)}