Block route rendering until auth and NSFW node classification are resolved

Hop-State: A_06FP9N1CECVZN3TAJZTJJ4R
Hop-Proposal: R_06FP9N09YGWW0S9XV4S3XNG
Hop-Task: T_06FP9MBVAVXP6ZRX0VG9RSG
Hop-Attempt: AT_06FP9MBVARDT7GQQ58C9WG0
This commit is contained in:
2026-07-15 01:10:54 -07:00
committed by Hop
parent 8935bdf5c5
commit c0b56f646b
4 changed files with 32 additions and 12 deletions
+3 -11
View File
@@ -10,6 +10,7 @@ import { Bot, Network, Server, EyeOff } from 'lucide-react';
import { useAuth } from '@/lib/contexts/AuthContext';
import { signedAPI } from '@/lib/api/signed-fetch';
import { AvatarImage } from '@/components/AvatarImage';
import { useRuntimeConfig } from '@/lib/contexts/ConfigContext';
interface User {
id: string;
@@ -85,6 +86,7 @@ interface SwarmPost {
export default function ExplorePage() {
const { user, did, handle } = useAuth();
const { config } = useRuntimeConfig();
const [query, setQuery] = useState('');
const [activeTab, setActiveTab] = useState<'node' | 'swarm' | 'users' | 'search'>('node');
const [nodePosts, setNodePosts] = useState<Post[]>([]);
@@ -99,17 +101,7 @@ export default function ExplorePage() {
const [loadingMore, setLoadingMore] = useState(false);
const [hasMoreNode, setHasMoreNode] = useState(true);
const [hasMoreSwarm, setHasMoreSwarm] = useState(true);
const [isNsfwNode, setIsNsfwNode] = useState(false);
// Fetch node info to check if NSFW
useEffect(() => {
fetch('/api/node')
.then(res => res.json())
.then(data => {
setIsNsfwNode(data.isNsfw || false);
})
.catch(() => { });
}, []);
const isNsfwNode = config?.isNsfw === true;
useEffect(() => {
// Load node posts (local only)
+4 -1
View File
@@ -4,9 +4,12 @@ import { usePathname } from 'next/navigation';
import { Sidebar } from './Sidebar';
import { RightSidebar } from './RightSidebar';
import { useAuth } from '@/lib/contexts/AuthContext';
import { useRuntimeConfig } from '@/lib/contexts/ConfigContext';
import { isAppBootstrapReady } from '@/lib/bootstrap/readiness';
export function LayoutWrapper({ children }: { children: React.ReactNode }) {
const { loading } = useAuth();
const { isLoading: configLoading } = useRuntimeConfig();
const pathname = usePathname();
// Paths that should NOT have the app layout
@@ -17,7 +20,7 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) {
// Hide right sidebar on chat page for more space
const hideRightSidebar = false;
if (loading) {
if (!isAppBootstrapReady({ authLoading: loading, configLoading })) {
return (
<div style={{
height: '100vh',
+16
View File
@@ -0,0 +1,16 @@
import { describe, expect, it } from 'vitest';
import { isAppBootstrapReady } from './readiness';
describe('isAppBootstrapReady', () => {
it('blocks route rendering while authentication is unresolved', () => {
expect(isAppBootstrapReady({ authLoading: true, configLoading: false })).toBe(false);
});
it('blocks route rendering while node classification is unresolved', () => {
expect(isAppBootstrapReady({ authLoading: false, configLoading: true })).toBe(false);
});
it('allows route rendering only after both dependencies are resolved', () => {
expect(isAppBootstrapReady({ authLoading: false, configLoading: false })).toBe(true);
});
});
+9
View File
@@ -0,0 +1,9 @@
export function isAppBootstrapReady({
authLoading,
configLoading,
}: {
authLoading: boolean;
configLoading: boolean;
}): boolean {
return !authLoading && !configLoading;
}