From 5ed10c407799973032794db18880b883d7932eef Mon Sep 17 00:00:00 2001 From: mawkone Date: Fri, 12 Jun 2026 11:57:36 -0700 Subject: [PATCH] feat(preview): make address bar interactive for routing inside iframe --- .../[projectId]/(home)/preview/page.tsx | 13 +++++++-- .../preview-toolbar/preview-toolbar-state.ts | 4 +++ .../components/project/project-icon-rail.tsx | 28 +++++++++++++++---- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/vibn-frontend/app/[workspace]/project/[projectId]/(home)/preview/page.tsx b/vibn-frontend/app/[workspace]/project/[projectId]/(home)/preview/page.tsx index d50c9d46..91d8d891 100644 --- a/vibn-frontend/app/[workspace]/project/[projectId]/(home)/preview/page.tsx +++ b/vibn-frontend/app/[workspace]/project/[projectId]/(home)/preview/page.tsx @@ -152,6 +152,7 @@ export default function PreviewTab() { const deviceMode = usePreviewToolbarStore((s) => s.deviceMode); const refreshKey = usePreviewToolbarStore((s) => s.refreshKey); + const currentPath = usePreviewToolbarStore((s) => s.currentPath); // When the user clicks the manual refresh button in the toolbar, we don't // just want to reload the iframe — we also want to trigger the same ghost/zombie @@ -166,8 +167,16 @@ export default function PreviewTab() { }, [refreshKey]); useLayoutEffect(() => { - setIframeSrc(primaryRunning?.url ?? null); - }, [primaryRunning?.url]); + if (!primaryRunning?.url) { + setIframeSrc(null); + } else { + const base = primaryRunning.url.replace(/\/$/, ""); + const path = currentPath.startsWith("/") + ? currentPath + : `/${currentPath}`; + setIframeSrc(`${base}${path}`); + } + }, [primaryRunning?.url, currentPath]); useEffect(() => { if (!bridge || !iframeSrc || !iframeDomRef.current) return; diff --git a/vibn-frontend/components/project/preview-toolbar/preview-toolbar-state.ts b/vibn-frontend/components/project/preview-toolbar/preview-toolbar-state.ts index ffa672fe..796a3543 100644 --- a/vibn-frontend/components/project/preview-toolbar/preview-toolbar-state.ts +++ b/vibn-frontend/components/project/preview-toolbar/preview-toolbar-state.ts @@ -5,6 +5,8 @@ interface PreviewToolbarState { setDeviceMode: (mode: "desktop" | "tablet" | "mobile") => void; refreshKey: number; triggerRefresh: () => void; + currentPath: string; + setCurrentPath: (path: string) => void; } export const usePreviewToolbarStore = create((set) => ({ @@ -12,4 +14,6 @@ export const usePreviewToolbarStore = create((set) => ({ setDeviceMode: (mode) => set({ deviceMode: mode }), refreshKey: 0, triggerRefresh: () => set((state) => ({ refreshKey: state.refreshKey + 1 })), + currentPath: "/", + setCurrentPath: (path) => set({ currentPath: path }), })); diff --git a/vibn-frontend/components/project/project-icon-rail.tsx b/vibn-frontend/components/project/project-icon-rail.tsx index 94949efc..a3bc4aa4 100644 --- a/vibn-frontend/components/project/project-icon-rail.tsx +++ b/vibn-frontend/components/project/project-icon-rail.tsx @@ -92,6 +92,8 @@ function PreviewDeviceToggles() { const deviceMode = usePreviewToolbarStore((s) => s.deviceMode); const setDeviceMode = usePreviewToolbarStore((s) => s.setDeviceMode); const triggerRefresh = usePreviewToolbarStore((s) => s.triggerRefresh); + const currentPath = usePreviewToolbarStore((s) => s.currentPath); + const setCurrentPath = usePreviewToolbarStore((s) => s.setCurrentPath); const params = useParams(); const projectId = params?.projectId as string; @@ -157,12 +159,23 @@ function PreviewDeviceToggles() { }} /> - 🌐 + + / + + setCurrentPath("/" + e.target.value.replace(/^\//, "")) + } + placeholder="path (e.g. dashboard)" style={{ background: "transparent", border: "none", @@ -172,7 +185,12 @@ function PreviewDeviceToggles() { color: "#18181b", fontSize: "0.75rem", textOverflow: "ellipsis", - paddingLeft: 4, + fontFamily: "var(--font-mono), monospace", + }} + onKeyDown={(e) => { + if (e.key === "Enter") { + triggerRefresh(); // force reload iframe + } }} />