@@ -2,6 +2,7 @@ import { BrowserRouter, Routes, Route, Navigate, useNavigate, useLocation, useSe
22import { useState , useEffect } from 'react' ;
33import { ObjectForm } from '@object-ui/plugin-form' ;
44import { Dialog , DialogContent , DialogHeader , DialogTitle , DialogDescription , Empty , EmptyTitle } from '@object-ui/components' ;
5+ import { toast } from 'sonner' ;
56import { SchemaRendererProvider } from '@object-ui/react' ;
67import { ObjectStackAdapter } from './dataSource' ;
78import type { ConnectionState } from './dataSource' ;
@@ -20,6 +21,8 @@ import { PageView } from './components/PageView';
2021import { ReportView } from './components/ReportView' ;
2122import { ExpressionProvider } from './context/ExpressionProvider' ;
2223import { ConditionalAuthWrapper } from './components/ConditionalAuthWrapper' ;
24+ import { KeyboardShortcutsDialog } from './components/KeyboardShortcutsDialog' ;
25+ import { useRecentItems } from './hooks/useRecentItems' ;
2326
2427// Auth Pages
2528import { LoginPage } from './pages/LoginPage' ;
@@ -35,6 +38,7 @@ import { ProfilePage } from './pages/system/ProfilePage';
3538
3639import { useParams } from 'react-router-dom' ;
3740import { ThemeProvider } from './components/theme-provider' ;
41+ import { ConsoleToaster } from './components/ConsoleToaster' ;
3842
3943export function AppContent ( ) {
4044 const [ dataSource , setDataSource ] = useState < ObjectStackAdapter | null > ( null ) ;
@@ -55,6 +59,7 @@ export function AppContent() {
5559 const [ isDialogOpen , setIsDialogOpen ] = useState ( false ) ;
5660 const [ editingRecord , setEditingRecord ] = useState < any > ( null ) ;
5761 const [ refreshKey , setRefreshKey ] = useState ( 0 ) ;
62+ const { addRecentItem } = useRecentItems ( ) ;
5863
5964 // Branding is now applied by AppShell via ConsoleLayout
6065
@@ -116,6 +121,47 @@ export function AppContent() {
116121
117122 const currentObjectDef = allObjects . find ( ( o : any ) => o . name === objectNameFromPath ) ;
118123
124+ // Track recent items on route change
125+ // Only depend on location.pathname — the sole external trigger.
126+ // All other values (activeApp, allObjects, cleanParts) are derived from
127+ // stable module-level config and the current pathname, so they don't need
128+ // to be in the dependency array (and including array refs would loop).
129+ useEffect ( ( ) => {
130+ if ( ! activeApp ) return ;
131+ const parts = location . pathname . split ( '/' ) . filter ( Boolean ) ;
132+ let objName = parts [ 2 ] ;
133+ if ( objName === 'view' || objName === 'record' || objName === 'page' || objName === 'dashboard' ) {
134+ objName = '' ;
135+ }
136+ const basePath = `/apps/${ activeApp . name } ` ;
137+ const objects = appConfig . objects || [ ] ;
138+ if ( objName ) {
139+ const obj = objects . find ( ( o : any ) => o . name === objName ) ;
140+ if ( obj ) {
141+ addRecentItem ( {
142+ id : `object:${ obj . name } ` ,
143+ label : obj . label || obj . name ,
144+ href : `${ basePath } /${ obj . name } ` ,
145+ type : 'object' ,
146+ } ) ;
147+ }
148+ } else if ( parts [ 2 ] === 'dashboard' && parts [ 3 ] ) {
149+ addRecentItem ( {
150+ id : `dashboard:${ parts [ 3 ] } ` ,
151+ label : parts [ 3 ] . replace ( / [ - _ ] / g, ' ' ) . replace ( / \b \w / g, ( c : string ) => c . toUpperCase ( ) ) ,
152+ href : `${ basePath } /dashboard/${ parts [ 3 ] } ` ,
153+ type : 'dashboard' ,
154+ } ) ;
155+ } else if ( parts [ 2 ] === 'report' && parts [ 3 ] ) {
156+ addRecentItem ( {
157+ id : `report:${ parts [ 3 ] } ` ,
158+ label : parts [ 3 ] . replace ( / [ - _ ] / g, ' ' ) . replace ( / \b \w / g, ( c : string ) => c . toUpperCase ( ) ) ,
159+ href : `${ basePath } /report/${ parts [ 3 ] } ` ,
160+ type : 'report' ,
161+ } ) ;
162+ }
163+ } , [ location . pathname , addRecentItem ] ) ; // eslint-disable-line react-hooks/exhaustive-deps
164+
119165 const handleEdit = ( record : any ) => {
120166 setEditingRecord ( record ) ;
121167 setIsDialogOpen ( true ) ;
@@ -166,6 +212,7 @@ export function AppContent() {
166212 objects = { allObjects }
167213 onAppChange = { handleAppChange }
168214 />
215+ < KeyboardShortcutsDialog />
169216 < SchemaRendererProvider dataSource = { dataSource || { } } >
170217 < ErrorBoundary >
171218 < Routes >
@@ -242,7 +289,15 @@ export function AppContent() {
242289 ? currentObjectDef . fields . map ( ( f : any ) => typeof f === 'string' ? f : f . name )
243290 : Object . keys ( currentObjectDef . fields ) )
244291 : [ ] ,
245- onSuccess : ( ) => { setIsDialogOpen ( false ) ; setRefreshKey ( k => k + 1 ) ; } ,
292+ onSuccess : ( ) => {
293+ setIsDialogOpen ( false ) ;
294+ setRefreshKey ( k => k + 1 ) ;
295+ toast . success (
296+ editingRecord
297+ ? `${ currentObjectDef ?. label } updated successfully`
298+ : `${ currentObjectDef ?. label } created successfully`
299+ ) ;
300+ } ,
246301 onCancel : ( ) => setIsDialogOpen ( false ) ,
247302 showSubmit : true ,
248303 showCancel : true ,
@@ -292,6 +347,7 @@ function RootRedirect() {
292347export function App ( ) {
293348 return (
294349 < ThemeProvider defaultTheme = "system" storageKey = "object-ui-theme" >
350+ < ConsoleToaster position = "bottom-right" />
295351 < ConditionalAuthWrapper authUrl = "/api/auth" >
296352 < BrowserRouter basename = "/" >
297353 < Routes >
0 commit comments