diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 98be888..a5b3e33 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -8,10 +8,12 @@ import { useToast } from '@/lib/contexts/ToastContext'; import { useAccentColor } from '@/lib/contexts/AccentColorContext'; import { getStorageProvider, MediaUploadError, uploadMediaFile } from '@/lib/stuffbox/browser-upload'; import { hasUnsavedChanges } from '@/lib/forms/dirty-state'; +import { useRuntimeConfig } from '@/lib/contexts/ConfigContext'; export default function AdminPage() { const { showToast } = useToast(); const { refreshAccentColor } = useAccentColor(); + const { setNodeNsfw } = useRuntimeConfig(); const [isAdmin, setIsAdmin] = useState(null); const [loading, setLoading] = useState(false); const [nodeSettings, setNodeSettings] = useState({ @@ -93,6 +95,11 @@ export default function AdminPage() { savedNodeSettingsRef.current = payload; const data = await res.json().catch(() => ({})); if (data.node) { + // Keep the global local-node classification and the sidebar's + // node payload in the same React batch. Otherwise profile + // media briefly sees "remote NSFW node on a safe local node" + // and applies a blur until the next full config refresh. + setNodeNsfw(data.node.isNsfw === true); window.dispatchEvent(new CustomEvent('synapsis:node-updated', { detail: data.node })); } showToast('Settings saved!', 'success'); diff --git a/src/components/RightSidebar.tsx b/src/components/RightSidebar.tsx index 67660a4..053e140 100644 --- a/src/components/RightSidebar.tsx +++ b/src/components/RightSidebar.tsx @@ -4,6 +4,7 @@ import { useState, useEffect } from 'react'; import Link from 'next/link'; import { AvatarImage } from './AvatarImage'; import { ProfileBanner } from './ProfileBanner'; +import { useRuntimeConfig } from '@/lib/contexts/ConfigContext'; interface Admin { handle: string; @@ -34,6 +35,8 @@ function formatNetworkTotal(value: number | undefined): string { } export function RightSidebar() { + const { config } = useRuntimeConfig(); + const localNodeIsNsfw = config?.isNsfw ?? false; const fallbackDescription = process.env.NEXT_PUBLIC_NODE_DESCRIPTION || 'A swarm social network node.'; const [nodeInfo, setNodeInfo] = useState({ name: process.env.NEXT_PUBLIC_NODE_NAME || 'Synapsis Node', @@ -138,7 +141,7 @@ export function RightSidebar() { {nodeInfo.bannerUrl && ( @@ -187,7 +190,7 @@ export function RightSidebar() { style={{ display: 'flex', alignItems: 'center', gap: '10px', textDecoration: 'none', color: 'inherit' }} >
- +
diff --git a/src/lib/contexts/ConfigContext.tsx b/src/lib/contexts/ConfigContext.tsx index b6087bf..712c7fa 100644 --- a/src/lib/contexts/ConfigContext.tsx +++ b/src/lib/contexts/ConfigContext.tsx @@ -1,6 +1,6 @@ 'use client'; -import { createContext, useContext, useEffect, useState, ReactNode } from 'react'; +import { createContext, useCallback, useContext, useEffect, useState, ReactNode } from 'react'; interface RuntimeConfig { domain: string; @@ -10,17 +10,23 @@ interface RuntimeConfig { interface ConfigContextType { config: RuntimeConfig | null; isLoading: boolean; + setNodeNsfw: (isNsfw: boolean) => void; } const ConfigContext = createContext({ config: null, isLoading: true, + setNodeNsfw: () => { }, }); export function ConfigProvider({ children }: { children: ReactNode }) { const [config, setConfig] = useState(null); const [isLoading, setIsLoading] = useState(true); + const setNodeNsfw = useCallback((isNsfw: boolean) => { + setConfig((current) => current ? { ...current, isNsfw } : current); + }, []); + useEffect(() => { // Fetch runtime config on mount fetch('/api/config') @@ -44,7 +50,7 @@ export function ConfigProvider({ children }: { children: ReactNode }) { }, []); return ( - + {children} );