Persist iframe sub-page in URL hash so refresh returns to the same page
On every iframe load, mirror its current path to the portal URL hash (e.g. /app/cashup#/reports). On mount, initialSrc reads that hash and restores the correct page after a refresh. Hash is cleared when switching apps so it doesn't bleed across slugs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2b4de33289
commit
51c99f1ee9
1 changed files with 29 additions and 2 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect, useRef } from 'react'
|
||||||
import { useParams, useNavigate } from 'react-router-dom'
|
import { useParams, useNavigate } from 'react-router-dom'
|
||||||
import { ChevronRight } from 'lucide-react'
|
import { ChevronRight } from 'lucide-react'
|
||||||
import { Sidebar } from '../components/Sidebar'
|
import { Sidebar } from '../components/Sidebar'
|
||||||
|
|
@ -9,6 +9,19 @@ export function AppFrame({ user }: { user: User }) {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const app = user.apps.find(a => a.slug === slug)
|
const app = user.apps.find(a => a.slug === slug)
|
||||||
const [menuOpen, setMenuOpen] = useState(false)
|
const [menuOpen, setMenuOpen] = useState(false)
|
||||||
|
const iframeRef = useRef<HTMLIFrameElement>(null)
|
||||||
|
|
||||||
|
// Capture hash once at mount — before iframe navigation overwrites it
|
||||||
|
const [initialSrc] = useState(() => {
|
||||||
|
if (!app) return ''
|
||||||
|
const sub = window.location.hash.slice(1)
|
||||||
|
return sub && sub !== '/' ? `${app.base_path}${sub}` : `${app.base_path}/`
|
||||||
|
})
|
||||||
|
|
||||||
|
// Clear hash when switching apps
|
||||||
|
useEffect(() => {
|
||||||
|
return () => { history.replaceState(null, '', window.location.pathname) }
|
||||||
|
}, [slug])
|
||||||
|
|
||||||
// Auto-close when user clicks into / navigates within the iframe
|
// Auto-close when user clicks into / navigates within the iframe
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -18,6 +31,18 @@ export function AppFrame({ user }: { user: User }) {
|
||||||
return () => window.removeEventListener('blur', close)
|
return () => window.removeEventListener('blur', close)
|
||||||
}, [menuOpen])
|
}, [menuOpen])
|
||||||
|
|
||||||
|
function handleIframeLoad() {
|
||||||
|
try {
|
||||||
|
const iframePath = iframeRef.current?.contentWindow?.location.pathname ?? ''
|
||||||
|
const relative = iframePath.replace(app!.base_path, '') || '/'
|
||||||
|
if (relative !== '/') {
|
||||||
|
history.replaceState(null, '', `/app/${slug}#${relative}`)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// cross-origin guard — won't trigger in this same-origin stack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!app) {
|
if (!app) {
|
||||||
return (
|
return (
|
||||||
<div style={{ height: '100dvh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
<div style={{ height: '100dvh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||||
|
|
@ -75,10 +100,12 @@ export function AppFrame({ user }: { user: User }) {
|
||||||
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
|
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
|
||||||
<div style={{ height: '3px', background: app.theme_color, flexShrink: 0 }} />
|
<div style={{ height: '3px', background: app.theme_color, flexShrink: 0 }} />
|
||||||
<iframe
|
<iframe
|
||||||
src={`${app.base_path}/`}
|
ref={iframeRef}
|
||||||
|
src={initialSrc}
|
||||||
title={app.name}
|
title={app.name}
|
||||||
style={{ flex: 1, border: 'none', width: '100%' }}
|
style={{ flex: 1, border: 'none', width: '100%' }}
|
||||||
allow="clipboard-read; clipboard-write"
|
allow="clipboard-read; clipboard-write"
|
||||||
|
onLoad={handleIframeLoad}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue