From 36d5faf67d6544b1d92a2ba1abe438541c90f75b Mon Sep 17 00:00:00 2001 From: AR 15 M4 Date: Tue, 24 Mar 2026 18:52:21 +0500 Subject: [PATCH] fix(preview): enable block toggle via localStorage when API is offline BlockMetaBar now falls back to localStorage for preview toggle when NestJS API is unavailable. PreviewClient reads localStorage state and listens for bb-preview-change events to stay in sync. Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/web/app/pages/preview/PreviewClient.tsx | 14 +++ apps/web/components/ui/BlockMetaBar.tsx | 89 +++++++++++--------- 2 files changed, 65 insertions(+), 38 deletions(-) diff --git a/apps/web/app/pages/preview/PreviewClient.tsx b/apps/web/app/pages/preview/PreviewClient.tsx index e9dd22d..8fd7491 100644 --- a/apps/web/app/pages/preview/PreviewClient.tsx +++ b/apps/web/app/pages/preview/PreviewClient.tsx @@ -11,6 +11,7 @@ import { ContactFormsBlock } from "@/components/blocks/ContactFormsBlock"; import { FooterBlock } from "@/components/blocks/FooterBlock"; const STORAGE_KEY = "bb-preview-created"; +const LS_PREFIX = "bb-block-preview:"; function BlockPlaceholder({ name, href }: { name: string; href: string }) { return ( @@ -119,11 +120,17 @@ export function PreviewClient() { const apiUrl = process.env.NEXT_PUBLIC_API_URL; + // Counter to force re-render when localStorage preview toggles change + const [, setRefresh] = useState(0); + useEffect(() => { setMounted(true); if (localStorage.getItem(STORAGE_KEY) === "true") { setCreated(true); } + const handler = () => setRefresh((n) => n + 1); + window.addEventListener("bb-preview-change", handler); + return () => window.removeEventListener("bb-preview-change", handler); }, []); useEffect(() => { @@ -141,9 +148,16 @@ export function PreviewClient() { }, [apiUrl]); function isReady(block: BlockDef): boolean { + // 1. API online → use API data if (apiMeta !== null && block.path in apiMeta) { return apiMeta[block.path] && !!block.component; } + // 2. Check localStorage (set by BlockMetaBar toggle) + const lsVal = typeof window !== "undefined" ? localStorage.getItem(LS_PREFIX + block.path) : null; + if (lsVal !== null) { + return lsVal === "true" && !!block.component; + } + // 3. Fallback to defaultReady return block.defaultReady && !!block.component; } diff --git a/apps/web/components/ui/BlockMetaBar.tsx b/apps/web/components/ui/BlockMetaBar.tsx index a80da4c..2dccd1e 100644 --- a/apps/web/components/ui/BlockMetaBar.tsx +++ b/apps/web/components/ui/BlockMetaBar.tsx @@ -2,6 +2,20 @@ import { useState, useEffect } from "react"; +const LS_PREFIX = "bb-block-preview:"; + +function readLocalPreview(path: string, fallback: boolean): boolean { + if (typeof window === "undefined") return fallback; + const v = localStorage.getItem(LS_PREFIX + path); + if (v === null) return fallback; + return v === "true"; +} + +function writeLocalPreview(path: string, value: boolean) { + localStorage.setItem(LS_PREFIX + path, String(value)); + window.dispatchEvent(new Event("bb-preview-change")); +} + interface BlockMeta { path: string; name: string; @@ -22,9 +36,14 @@ export function BlockMetaBar({ path, defaultVersion, defaultIsInPreview }: Block const [saving, setSaving] = useState(false); const [togglingPreview, setTogglingPreview] = useState(false); const [apiDown, setApiDown] = useState(false); + const [localPreview, setLocalPreview] = useState(defaultIsInPreview); const apiUrl = process.env.NEXT_PUBLIC_API_URL; + useEffect(() => { + setLocalPreview(readLocalPreview(path, defaultIsInPreview)); + }, [path, defaultIsInPreview]); + useEffect(() => { if (!apiUrl) { setApiDown(true); return; } fetch(`${apiUrl}/blocks/by-path?path=${encodeURIComponent(path)}`) @@ -58,6 +77,13 @@ export function BlockMetaBar({ path, defaultVersion, defaultIsInPreview }: Block } async function togglePreview() { + if (apiDown) { + // Fallback: toggle via localStorage + const newVal = !localPreview; + setLocalPreview(newVal); + writeLocalPreview(path, newVal); + return; + } if (!meta) return; setTogglingPreview(true); try { @@ -69,7 +95,7 @@ export function BlockMetaBar({ path, defaultVersion, defaultIsInPreview }: Block } const version = meta?.version ?? defaultVersion; - const isInPreview = meta?.isInPreview ?? defaultIsInPreview; + const isInPreview = apiDown ? localPreview : (meta?.isInPreview ?? localPreview); return (
· {/* Preview toggle */} - {apiDown ? ( + - )} + className="inline-block w-1.5 h-1.5 rounded-full" + style={{ background: isInPreview ? "#22c55e" : "#d1d5db" }} + /> + {togglingPreview + ? '...' + : isInPreview + ? "В превью" + : "Не в превью"} + {apiDown && ( <>