From 091c85080b61e8394611c46a38fc368079a75796 Mon Sep 17 00:00:00 2001 From: AskIt Date: Mon, 26 Jan 2026 01:44:51 +0100 Subject: [PATCH] feat(auth,ui): Add dynamic node branding and NSFW content gating - Replace hardcoded SynapsisLogo with dynamic Image component for custom node logos - Add logoUrl support to node info state with proper TypeScript typing - Implement nodeInfoLoaded state to ensure proper rendering timing - Add NSFW node detection and content gating for unauthenticated users - Display custom node logo on login page when available, fallback to default - Show adult content warning gate on home page for NSFW nodes - Improve error handling in node info fetch with explicit state updates - Add EyeOff icon for NSFW content warning display - Enhance login page layout with minHeight for consistent spacing during load --- src/app/login/page.tsx | 56 +++++++++++++++++++++++++++----------- src/app/page.tsx | 28 ++++++++++++++++++- src/components/Sidebar.tsx | 24 ++++++++-------- 3 files changed, 80 insertions(+), 28 deletions(-) diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 39c5546..a7ad8ec 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'; import Link from 'next/link'; -import { SynapsisLogo } from '@/components/Icons'; +import Image from 'next/image'; import { TriangleAlert } from 'lucide-react'; export default function LoginPage() { @@ -14,7 +14,8 @@ export default function LoginPage() { const [displayName, setDisplayName] = useState(''); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); - const [nodeInfo, setNodeInfo] = useState({ name: '', description: '' }); + const [nodeInfoLoaded, setNodeInfoLoaded] = useState(false); + const [nodeInfo, setNodeInfo] = useState<{ name: string; description: string; logoUrl?: string }>({ name: '', description: '' }); const [handleStatus, setHandleStatus] = useState<'idle' | 'checking' | 'available' | 'taken'>('idle'); // Import specific state @@ -31,10 +32,14 @@ export default function LoginPage() { .then(data => { setNodeInfo({ name: data.name || '', - description: data.description || 'Synapsis is designed to function like a global signal layer rather than a culture-bound platform. Anyone can run their own node and still participate in a shared, interconnected network, with global identity, clean terminology, and a modern interface that feels current rather than experimental. Synapsis aims to be neutral, resilient infrastructure for human and machine discourse, more like a protocol or nervous system than a social club.' + description: data.description || 'Synapsis is designed to function like a global signal layer rather than a culture-bound platform. Anyone can run their own node and still participate in a shared, interconnected network, with global identity, clean terminology, and a modern interface that feels current rather than experimental. Synapsis aims to be neutral, resilient infrastructure for human and machine discourse, more like a protocol or nervous system than a social club.', + logoUrl: data.logoUrl || undefined }); + setNodeInfoLoaded(true); }) - .catch(() => { }); + .catch(() => { + setNodeInfoLoaded(true); + }); }, []); // Handle availability check @@ -156,19 +161,38 @@ export default function LoginPage() { }}>
{/* Logo */} -
-
- - Synapsis -
- {nodeInfo.name && nodeInfo.name !== 'Synapsis' && ( -
- {nodeInfo.name} -
+
+ {nodeInfoLoaded && ( + <> + {nodeInfo.logoUrl ? ( + {nodeInfo.name + ) : ( + Synapsis + )} + {nodeInfo.name && nodeInfo.name !== 'Synapsis' && !nodeInfo.logoUrl && ( +
+ {nodeInfo.name} +
+ )} +

+ {nodeInfo.description} +

+ )} -

- {nodeInfo.description} -

{/* Mode Switcher */} diff --git a/src/app/page.tsx b/src/app/page.tsx index 7512ba6..782d1a1 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -6,6 +6,7 @@ import { useAuth } from '@/lib/contexts/AuthContext'; import { PostCard } from '@/components/PostCard'; import { Compose } from '@/components/Compose'; import { Post } from '@/lib/types'; +import { EyeOff } from 'lucide-react'; interface FeedMeta { score: number; @@ -23,6 +24,7 @@ export default function Home() { const [loading, setLoading] = useState(true); const [replyingTo, setReplyingTo] = useState(null); const [feedType, setFeedType] = useState<'latest' | 'curated'>('latest'); + const [isNsfwNode, setIsNsfwNode] = useState(false); const [feedMeta, setFeedMeta] = useState<{ algorithm: string; windowHours: number; @@ -35,6 +37,16 @@ export default function Home() { }; } | null>(null); + // Fetch node info to check if NSFW + useEffect(() => { + fetch('/api/node') + .then(res => res.json()) + .then(data => { + setIsNsfwNode(data.isNsfw || false); + }) + .catch(() => {}); + }, []); + const loadFeed = async (type: 'latest' | 'curated') => { setLoading(true); try { @@ -145,7 +157,21 @@ export default function Home() {
)} - {loading ? ( + {/* NSFW node gate for unauthenticated users */} + {!user && isNsfwNode ? ( +
+ +

+ Adult Content +

+

+ This node contains adult or sensitive content. You must be 18 or older and signed in to view posts. +

+ + Sign In to Continue + +
+ ) : loading ? (
Loading...
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index ba22ff5..cc39697 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -11,17 +11,17 @@ import { formatFullHandle } from '@/lib/utils/handle'; export function Sidebar() { const { user, isAdmin } = useAuth(); const pathname = usePathname(); - const [customLogoUrl, setCustomLogoUrl] = useState(null); + const [customLogoUrl, setCustomLogoUrl] = useState(undefined); useEffect(() => { fetch('/api/node') .then(res => res.json()) .then(data => { - if (data.logoUrl) { - setCustomLogoUrl(data.logoUrl); - } + setCustomLogoUrl(data.logoUrl || null); }) - .catch(() => {}); + .catch(() => { + setCustomLogoUrl(null); + }); }, []); // Home is exact match @@ -29,8 +29,8 @@ export function Sidebar() { return (