Prevent local admin profile media from blurring during NSFW node updates
Hop-State: A_06FPE5MS0ZXE3ESZJJ27C98 Hop-Proposal: R_06FPE5KR2QGEVQD0MW8DSZR Hop-Task: T_06FPE564WJTQYM53WG3W8P8 Hop-Attempt: AT_06FPE564WKTFF688KW6DM80
This commit is contained in:
@@ -8,10 +8,12 @@ import { useToast } from '@/lib/contexts/ToastContext';
|
|||||||
import { useAccentColor } from '@/lib/contexts/AccentColorContext';
|
import { useAccentColor } from '@/lib/contexts/AccentColorContext';
|
||||||
import { getStorageProvider, MediaUploadError, uploadMediaFile } from '@/lib/stuffbox/browser-upload';
|
import { getStorageProvider, MediaUploadError, uploadMediaFile } from '@/lib/stuffbox/browser-upload';
|
||||||
import { hasUnsavedChanges } from '@/lib/forms/dirty-state';
|
import { hasUnsavedChanges } from '@/lib/forms/dirty-state';
|
||||||
|
import { useRuntimeConfig } from '@/lib/contexts/ConfigContext';
|
||||||
|
|
||||||
export default function AdminPage() {
|
export default function AdminPage() {
|
||||||
const { showToast } = useToast();
|
const { showToast } = useToast();
|
||||||
const { refreshAccentColor } = useAccentColor();
|
const { refreshAccentColor } = useAccentColor();
|
||||||
|
const { setNodeNsfw } = useRuntimeConfig();
|
||||||
const [isAdmin, setIsAdmin] = useState<boolean | null>(null);
|
const [isAdmin, setIsAdmin] = useState<boolean | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [nodeSettings, setNodeSettings] = useState({
|
const [nodeSettings, setNodeSettings] = useState({
|
||||||
@@ -93,6 +95,11 @@ export default function AdminPage() {
|
|||||||
savedNodeSettingsRef.current = payload;
|
savedNodeSettingsRef.current = payload;
|
||||||
const data = await res.json().catch(() => ({}));
|
const data = await res.json().catch(() => ({}));
|
||||||
if (data.node) {
|
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 }));
|
window.dispatchEvent(new CustomEvent('synapsis:node-updated', { detail: data.node }));
|
||||||
}
|
}
|
||||||
showToast('Settings saved!', 'success');
|
showToast('Settings saved!', 'success');
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useState, useEffect } from 'react';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { AvatarImage } from './AvatarImage';
|
import { AvatarImage } from './AvatarImage';
|
||||||
import { ProfileBanner } from './ProfileBanner';
|
import { ProfileBanner } from './ProfileBanner';
|
||||||
|
import { useRuntimeConfig } from '@/lib/contexts/ConfigContext';
|
||||||
|
|
||||||
interface Admin {
|
interface Admin {
|
||||||
handle: string;
|
handle: string;
|
||||||
@@ -34,6 +35,8 @@ function formatNetworkTotal(value: number | undefined): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function RightSidebar() {
|
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 fallbackDescription = process.env.NEXT_PUBLIC_NODE_DESCRIPTION || 'A swarm social network node.';
|
||||||
const [nodeInfo, setNodeInfo] = useState<NodeInfo>({
|
const [nodeInfo, setNodeInfo] = useState<NodeInfo>({
|
||||||
name: process.env.NEXT_PUBLIC_NODE_NAME || 'Synapsis Node',
|
name: process.env.NEXT_PUBLIC_NODE_NAME || 'Synapsis Node',
|
||||||
@@ -138,7 +141,7 @@ export function RightSidebar() {
|
|||||||
{nodeInfo.bannerUrl && (
|
{nodeInfo.bannerUrl && (
|
||||||
<ProfileBanner
|
<ProfileBanner
|
||||||
url={nodeInfo.bannerUrl}
|
url={nodeInfo.bannerUrl}
|
||||||
nodeIsNsfw={nodeInfo.isNsfw}
|
nodeIsNsfw={localNodeIsNsfw}
|
||||||
height={140}
|
height={140}
|
||||||
borderBottom="1px solid var(--border)"
|
borderBottom="1px solid var(--border)"
|
||||||
/>
|
/>
|
||||||
@@ -187,7 +190,7 @@ export function RightSidebar() {
|
|||||||
style={{ display: 'flex', alignItems: 'center', gap: '10px', textDecoration: 'none', color: 'inherit' }}
|
style={{ display: 'flex', alignItems: 'center', gap: '10px', textDecoration: 'none', color: 'inherit' }}
|
||||||
>
|
>
|
||||||
<div className="avatar avatar-sm" style={{ flexShrink: 0 }}>
|
<div className="avatar avatar-sm" style={{ flexShrink: 0 }}>
|
||||||
<AvatarImage avatarUrl={admin.avatarUrl} seed={admin.handle} isNsfw={admin.isNsfw} nodeIsNsfw={nodeInfo.isNsfw} alt={admin.displayName || admin.handle} />
|
<AvatarImage avatarUrl={admin.avatarUrl} seed={admin.handle} isNsfw={admin.isNsfw} nodeIsNsfw={localNodeIsNsfw} alt={admin.displayName || admin.handle} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontWeight: 500, fontSize: '14px' }}>
|
<div style={{ fontWeight: 500, fontSize: '14px' }}>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
|
import { createContext, useCallback, useContext, useEffect, useState, ReactNode } from 'react';
|
||||||
|
|
||||||
interface RuntimeConfig {
|
interface RuntimeConfig {
|
||||||
domain: string;
|
domain: string;
|
||||||
@@ -10,17 +10,23 @@ interface RuntimeConfig {
|
|||||||
interface ConfigContextType {
|
interface ConfigContextType {
|
||||||
config: RuntimeConfig | null;
|
config: RuntimeConfig | null;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
setNodeNsfw: (isNsfw: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConfigContext = createContext<ConfigContextType>({
|
const ConfigContext = createContext<ConfigContextType>({
|
||||||
config: null,
|
config: null,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
setNodeNsfw: () => { },
|
||||||
});
|
});
|
||||||
|
|
||||||
export function ConfigProvider({ children }: { children: ReactNode }) {
|
export function ConfigProvider({ children }: { children: ReactNode }) {
|
||||||
const [config, setConfig] = useState<RuntimeConfig | null>(null);
|
const [config, setConfig] = useState<RuntimeConfig | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
|
const setNodeNsfw = useCallback((isNsfw: boolean) => {
|
||||||
|
setConfig((current) => current ? { ...current, isNsfw } : current);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Fetch runtime config on mount
|
// Fetch runtime config on mount
|
||||||
fetch('/api/config')
|
fetch('/api/config')
|
||||||
@@ -44,7 +50,7 @@ export function ConfigProvider({ children }: { children: ReactNode }) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfigContext.Provider value={{ config, isLoading }}>
|
<ConfigContext.Provider value={{ config, isLoading, setNodeNsfw }}>
|
||||||
{children}
|
{children}
|
||||||
</ConfigContext.Provider>
|
</ConfigContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user