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) <noreply@anthropic.com>
This commit is contained in:
@@ -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 (
|
||||
<div
|
||||
@@ -124,44 +150,31 @@ export function BlockMetaBar({ path, defaultVersion, defaultIsInPreview }: Block
|
||||
<span style={{ color: "var(--bb-border)" }}>·</span>
|
||||
|
||||
{/* Preview toggle */}
|
||||
{apiDown ? (
|
||||
<button
|
||||
onClick={togglePreview}
|
||||
disabled={togglingPreview || (!apiDown && !meta)}
|
||||
title={isInPreview ? "Убрать из превью страницы" : "Включить в превью страницы"}
|
||||
className="flex items-center gap-1.5 px-2.5 py-1 rounded font-medium transition-colors cursor-pointer"
|
||||
style={isInPreview ? {
|
||||
background: "#dcfce7",
|
||||
color: "#16a34a",
|
||||
border: "1px solid #86efac",
|
||||
} : {
|
||||
background: "var(--bb-sidebar-bg)",
|
||||
color: "var(--bb-text-muted)",
|
||||
border: "1px solid var(--bb-border)",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className="flex items-center gap-1"
|
||||
style={{ color: isInPreview ? "#16a34a" : "var(--bb-text-muted)" }}
|
||||
>
|
||||
<span
|
||||
className="inline-block w-1.5 h-1.5 rounded-full"
|
||||
style={{ background: isInPreview ? "#22c55e" : "#d1d5db" }}
|
||||
/>
|
||||
{isInPreview ? "В превью" : "Не в превью"}
|
||||
</span>
|
||||
) : (
|
||||
<button
|
||||
onClick={togglePreview}
|
||||
disabled={togglingPreview || !meta}
|
||||
title={isInPreview ? "Убрать из превью страницы" : "Включить в превью страницы"}
|
||||
className="flex items-center gap-1.5 px-2.5 py-1 rounded font-medium transition-colors"
|
||||
style={isInPreview ? {
|
||||
background: "#dcfce7",
|
||||
color: "#16a34a",
|
||||
border: "1px solid #86efac",
|
||||
} : {
|
||||
background: "var(--bb-sidebar-bg)",
|
||||
color: "var(--bb-text-muted)",
|
||||
border: "1px solid var(--bb-border)",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className="inline-block w-1.5 h-1.5 rounded-full"
|
||||
style={{ background: isInPreview ? "#22c55e" : "#d1d5db" }}
|
||||
/>
|
||||
{togglingPreview
|
||||
? '...'
|
||||
: isInPreview
|
||||
? "В превью"
|
||||
: "Добавить в превью"}
|
||||
</button>
|
||||
)}
|
||||
className="inline-block w-1.5 h-1.5 rounded-full"
|
||||
style={{ background: isInPreview ? "#22c55e" : "#d1d5db" }}
|
||||
/>
|
||||
{togglingPreview
|
||||
? '...'
|
||||
: isInPreview
|
||||
? "В превью"
|
||||
: "Не в превью"}
|
||||
</button>
|
||||
|
||||
{apiDown && (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user