A locally focused bluesky appview

profile page scrolling

+46 -18
+1 -1
frontend/public/index.html
··· 24 work correctly both with client-side routing and a non-root public URL. 25 Learn how to configure a non-root public URL by running `npm run build`. 26 --> 27 - <title>React App</title> 28 </head> 29 <body> 30 <noscript>You need to enable JavaScript to run this app.</noscript>
··· 24 work correctly both with client-side routing and a non-root public URL. 25 Learn how to configure a non-root public URL by running `npm run build`. 26 --> 27 + <title>Konbini</title> 28 </head> 29 <body> 30 <noscript>You need to enable JavaScript to run this app.</noscript>
+23
frontend/src/components/ProfilePage.css
··· 221 font-size: 16px; 222 } 223 224 @media (max-width: 600px) { 225 .profile-page { 226 margin: 0;
··· 221 font-size: 16px; 222 } 223 224 + .load-more-trigger { 225 + min-height: 20px; 226 + padding: 20px 0; 227 + } 228 + 229 + .loading-more { 230 + text-align: center; 231 + padding: 20px; 232 + color: #536471; 233 + font-size: 14px; 234 + } 235 + 236 + .end-of-feed { 237 + text-align: center; 238 + padding: 40px 20px; 239 + color: #657786; 240 + font-size: 14px; 241 + } 242 + 243 + .end-of-feed p { 244 + margin: 0; 245 + } 246 + 247 @media (max-width: 600px) { 248 .profile-page { 249 margin: 0;
+21 -16
frontend/src/components/ProfilePage.tsx
··· 1 - import React, { useState, useEffect, useRef } from 'react'; 2 import { useParams } from 'react-router-dom'; 3 import { ActorProfile, PostResponse } from '../types'; 4 import { ApiClient } from '../api'; ··· 67 fetchProfile(); 68 }, [account]); 69 70 - const fetchMorePosts = async (cursor: string) => { 71 if (!account || loadingMore || !hasMore) return; 72 73 try { 74 setLoadingMore(true); 75 - const data = await ApiClient.getProfilePosts(account, cursor); 76 setPosts(prev => [...prev, ...data.posts]); 77 setCursor(data.cursor || null); 78 setHasMore(!!(data.cursor && data.posts.length > 0)); ··· 81 } finally { 82 setLoadingMore(false); 83 } 84 - }; 85 86 useEffect(() => { 87 const observer = new IntersectionObserver( 88 (entries) => { 89 - if (entries[0].isIntersecting && hasMore && !loadingMore && !loading) { 90 - if (cursor) { 91 - fetchMorePosts(cursor); 92 - } 93 } 94 }, 95 { threshold: 0.1 } 96 ); 97 98 - if (observerTarget.current) { 99 - observer.observe(observerTarget.current); 100 } 101 102 return () => { 103 - if (observerTarget.current) { 104 - observer.unobserve(observerTarget.current); 105 } 106 }; 107 - }, [hasMore, loadingMore, loading, cursor]); 108 109 if (loading) { 110 return ( ··· 216 <p>{activeTab === 'posts' ? 'No posts yet' : 'No replies yet'}</p> 217 </div> 218 )} 219 - {hasMore && <div ref={observerTarget} style={{ height: '20px' }} />} 220 - {loadingMore && ( 221 - <div className="loading-more">Loading more posts...</div> 222 )} 223 </div> 224 </div>
··· 1 + import React, { useState, useEffect, useRef, useCallback } from 'react'; 2 import { useParams } from 'react-router-dom'; 3 import { ActorProfile, PostResponse } from '../types'; 4 import { ApiClient } from '../api'; ··· 67 fetchProfile(); 68 }, [account]); 69 70 + const fetchMorePosts = useCallback(async (cursorToUse: string) => { 71 if (!account || loadingMore || !hasMore) return; 72 73 try { 74 setLoadingMore(true); 75 + const data = await ApiClient.getProfilePosts(account, cursorToUse); 76 setPosts(prev => [...prev, ...data.posts]); 77 setCursor(data.cursor || null); 78 setHasMore(!!(data.cursor && data.posts.length > 0)); ··· 81 } finally { 82 setLoadingMore(false); 83 } 84 + }, [account, loadingMore, hasMore]); 85 86 useEffect(() => { 87 const observer = new IntersectionObserver( 88 (entries) => { 89 + if (entries[0].isIntersecting && hasMore && !loadingMore && !loading && cursor) { 90 + fetchMorePosts(cursor); 91 } 92 }, 93 { threshold: 0.1 } 94 ); 95 96 + const currentTarget = observerTarget.current; 97 + if (currentTarget) { 98 + observer.observe(currentTarget); 99 } 100 101 return () => { 102 + if (currentTarget) { 103 + observer.unobserve(currentTarget); 104 } 105 }; 106 + }, [hasMore, loadingMore, loading, cursor, fetchMorePosts]); 107 108 if (loading) { 109 return ( ··· 215 <p>{activeTab === 'posts' ? 'No posts yet' : 'No replies yet'}</p> 216 </div> 217 )} 218 + {hasMore && ( 219 + <div ref={observerTarget} className="load-more-trigger"> 220 + {loadingMore && <div className="loading-more">Loading more posts...</div>} 221 + </div> 222 + )} 223 + {!hasMore && posts.length > 0 && ( 224 + <div className="end-of-feed"> 225 + <p>You've reached the end!</p> 226 + </div> 227 )} 228 </div> 229 </div>
+1 -1
handlers.go
··· 149 150 // Get cursor from query parameter (timestamp in RFC3339 format) 151 cursor := e.QueryParam("cursor") 152 - limit := 20 153 154 tcursor := time.Now() 155 if cursor != "" {
··· 149 150 // Get cursor from query parameter (timestamp in RFC3339 format) 151 cursor := e.QueryParam("cursor") 152 + limit := 50 153 154 tcursor := time.Now() 155 if cursor != "" {