Update feed labels and improve chat UX
Renamed 'Latest' feed to 'Following' across the home and profile pages for clarity, and updated related UI text. Improved chat experience by adding a scroll-to-bottom button, tracking scroll position, and only auto-scrolling when the user is at the bottom. Updated default node description to 'A swarm social network node.' in multiple locations.
This commit is contained in:
@@ -34,7 +34,7 @@ export async function GET() {
|
||||
if (!node) {
|
||||
return NextResponse.json({
|
||||
name: process.env.NEXT_PUBLIC_NODE_NAME || 'Synapsis Node',
|
||||
description: process.env.NEXT_PUBLIC_NODE_DESCRIPTION || 'A federated social network node.',
|
||||
description: process.env.NEXT_PUBLIC_NODE_DESCRIPTION || 'A swarm social network node.',
|
||||
accentColor: process.env.NEXT_PUBLIC_ACCENT_COLOR || '#FFFFFF',
|
||||
domain,
|
||||
admins,
|
||||
@@ -52,7 +52,7 @@ export async function GET() {
|
||||
console.error('Node info error:', error);
|
||||
return NextResponse.json({
|
||||
name: process.env.NEXT_PUBLIC_NODE_NAME || 'Synapsis Node',
|
||||
description: process.env.NEXT_PUBLIC_NODE_DESCRIPTION || 'A federated social network node.',
|
||||
description: process.env.NEXT_PUBLIC_NODE_DESCRIPTION || 'A swarm social network node.',
|
||||
admins: [],
|
||||
});
|
||||
}
|
||||
|
||||
+74
-8
@@ -61,6 +61,28 @@ export default function ChatPage() {
|
||||
const [encryptionChecked, setEncryptionChecked] = useState(false);
|
||||
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
const messagesContainerRef = useRef<HTMLDivElement>(null);
|
||||
const [isAtBottom, setIsAtBottom] = useState(true);
|
||||
|
||||
// Check if user is scrolled to bottom
|
||||
const checkIfAtBottom = () => {
|
||||
if (!messagesContainerRef.current) return true;
|
||||
const { scrollTop, scrollHeight, clientHeight } = messagesContainerRef.current;
|
||||
const threshold = 100; // pixels from bottom
|
||||
return scrollHeight - scrollTop - clientHeight < threshold;
|
||||
};
|
||||
|
||||
// Handle scroll to track if user is at bottom
|
||||
const handleScroll = () => {
|
||||
setIsAtBottom(checkIfAtBottom());
|
||||
};
|
||||
|
||||
// Scroll to bottom manually
|
||||
const scrollToBottom = () => {
|
||||
if (messagesEndRef.current) {
|
||||
messagesEndRef.current.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
};
|
||||
|
||||
// Wait for encryption to be ready before showing UI
|
||||
useEffect(() => {
|
||||
@@ -106,12 +128,12 @@ export default function ChatPage() {
|
||||
}
|
||||
}, [selectedConversation, hasKeys]);
|
||||
|
||||
// Auto-scroll to bottom of messages
|
||||
// Auto-scroll to bottom of messages only if user was already at bottom
|
||||
useEffect(() => {
|
||||
if (messagesEndRef.current) {
|
||||
if (messagesEndRef.current && isAtBottom) {
|
||||
messagesEndRef.current.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
}, [messages]);
|
||||
}, [messages, isAtBottom]);
|
||||
|
||||
const fetchRecipientKey = async (handle: string) => {
|
||||
try {
|
||||
@@ -439,9 +461,9 @@ export default function ChatPage() {
|
||||
// Thread View
|
||||
if (selectedConversation) {
|
||||
return (
|
||||
<>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100vh', maxWidth: '600px', margin: '0 auto' }}>
|
||||
{/* Header */}
|
||||
<div className="post" style={{ position: 'sticky', top: 0, zIndex: 10, background: 'var(--background)', borderBottom: '1px solid var(--border)' }}>
|
||||
<div className="post" style={{ position: 'sticky', top: 0, zIndex: 10, background: 'var(--background)', borderBottom: '1px solid var(--border)', flexShrink: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||
<button
|
||||
onClick={() => setSelectedConversation(null)}
|
||||
@@ -479,7 +501,17 @@ export default function ChatPage() {
|
||||
</div>
|
||||
|
||||
{/* Messages */}
|
||||
<div style={{ padding: '16px', minHeight: 'calc(100vh - 250px)' }}>
|
||||
<div
|
||||
ref={messagesContainerRef}
|
||||
onScroll={handleScroll}
|
||||
style={{
|
||||
padding: '16px',
|
||||
flex: 1,
|
||||
overflowY: 'auto',
|
||||
paddingBottom: '16px',
|
||||
position: 'relative'
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
||||
{messages.map(msg => (
|
||||
<div key={msg.id} style={{
|
||||
@@ -524,10 +556,44 @@ export default function ChatPage() {
|
||||
))}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
|
||||
{/* Scroll to bottom button */}
|
||||
{!isAtBottom && (
|
||||
<button
|
||||
onClick={scrollToBottom}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: '16px',
|
||||
right: '24px',
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
borderRadius: '50%',
|
||||
background: 'var(--accent)',
|
||||
color: '#000',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.2)',
|
||||
zIndex: 5
|
||||
}}
|
||||
aria-label="Scroll to bottom"
|
||||
>
|
||||
↓
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Input */}
|
||||
<div className="compose">
|
||||
<div
|
||||
className="compose"
|
||||
style={{
|
||||
borderTop: '1px solid var(--border)',
|
||||
background: 'var(--background)',
|
||||
flexShrink: 0
|
||||
}}
|
||||
>
|
||||
<form onSubmit={sendMessage} style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
||||
<input
|
||||
type="text"
|
||||
@@ -546,7 +612,7 @@ export default function ChatPage() {
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+15
-6
@@ -27,7 +27,7 @@ export default function Home() {
|
||||
const [loadingMore, setLoadingMore] = useState(false);
|
||||
const [nextCursor, setNextCursor] = useState<string | null>(null);
|
||||
const [replyingTo, setReplyingTo] = useState<Post | null>(null);
|
||||
const [feedType, setFeedType] = useState<'latest' | 'curated'>('latest');
|
||||
const [feedType, setFeedType] = useState<'following' | 'curated'>('following');
|
||||
const [feedMeta, setFeedMeta] = useState<{
|
||||
algorithm: string;
|
||||
windowHours: number;
|
||||
@@ -49,7 +49,7 @@ export default function Home() {
|
||||
}
|
||||
}, [user, router]);
|
||||
|
||||
const loadFeed = async (type: 'latest' | 'curated', cursor?: string | null) => {
|
||||
const loadFeed = async (type: 'following' | 'curated', cursor?: string | null) => {
|
||||
if (cursor) {
|
||||
setLoadingMore(true);
|
||||
} else {
|
||||
@@ -175,10 +175,10 @@ export default function Home() {
|
||||
<h1 style={{ fontSize: '18px', fontWeight: 600 }}>Home</h1>
|
||||
<div className="feed-toggle">
|
||||
<button
|
||||
className={`feed-toggle-btn ${feedType === 'latest' ? 'active' : ''}`}
|
||||
onClick={() => setFeedType('latest')}
|
||||
className={`feed-toggle-btn ${feedType === 'following' ? 'active' : ''}`}
|
||||
onClick={() => setFeedType('following')}
|
||||
>
|
||||
Latest
|
||||
Following
|
||||
</button>
|
||||
<button
|
||||
className={`feed-toggle-btn ${feedType === 'curated' ? 'active' : ''}`}
|
||||
@@ -196,6 +196,15 @@ export default function Home() {
|
||||
onCancelReply={() => setReplyingTo(null)}
|
||||
/>
|
||||
|
||||
{feedType === 'following' && (
|
||||
<div className="feed-meta card">
|
||||
<div className="feed-meta-title">Following feed</div>
|
||||
<div className="feed-meta-body">
|
||||
This feed shows posts from accounts you follow in chronological order, with the most recent posts appearing first.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{feedType === 'curated' && feedMeta && (
|
||||
<div className="feed-meta card">
|
||||
<div className="feed-meta-title">Curated feed</div>
|
||||
@@ -216,7 +225,7 @@ export default function Home() {
|
||||
<p>No posts from the swarm yet</p>
|
||||
<p style={{ fontSize: '13px', marginTop: '8px' }}>
|
||||
The curated feed shows posts from other nodes in the Synapsis network.
|
||||
Check back later as nodes are discovered, or switch to Latest to see posts from people you follow.
|
||||
Check back later as nodes are discovered, or switch to Following to see posts from people you follow.
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
|
||||
+12
-12
@@ -636,18 +636,6 @@ export default function ProfilePage() {
|
||||
)}
|
||||
|
||||
<div style={{ display: 'flex', gap: '16px', marginTop: '12px' }}>
|
||||
<button
|
||||
onClick={() => setActiveTab('following')}
|
||||
style={{
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
color: 'var(--foreground)',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
<strong>{user.followingCount}</strong>{' '}
|
||||
<span style={{ color: 'var(--foreground-tertiary)' }}>Following</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('followers')}
|
||||
style={{
|
||||
@@ -660,6 +648,18 @@ export default function ProfilePage() {
|
||||
<strong>{user.followersCount}</strong>{' '}
|
||||
<span style={{ color: 'var(--foreground-tertiary)' }}>Followers</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('following')}
|
||||
style={{
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
color: 'var(--foreground)',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
<strong>{user.followingCount}</strong>{' '}
|
||||
<span style={{ color: 'var(--foreground-tertiary)' }}>Following</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@ interface Admin {
|
||||
}
|
||||
|
||||
export function RightSidebar() {
|
||||
const fallbackDescription = process.env.NEXT_PUBLIC_NODE_DESCRIPTION || 'A federated social network node.';
|
||||
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',
|
||||
description: fallbackDescription,
|
||||
|
||||
Reference in New Issue
Block a user