+1
-1
frontend/public/index.html
+1
-1
frontend/public/index.html
···
24
24
work correctly both with client-side routing and a non-root public URL.
25
25
Learn how to configure a non-root public URL by running `npm run build`.
26
26
-->
27
-
<title>React App</title>
27
+
<title>Konbini</title>
28
28
</head>
29
29
<body>
30
30
<noscript>You need to enable JavaScript to run this app.</noscript>
+23
frontend/src/components/ProfilePage.css
+23
frontend/src/components/ProfilePage.css
···
221
221
font-size: 16px;
222
222
}
223
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
+
224
247
@media (max-width: 600px) {
225
248
.profile-page {
226
249
margin: 0;
+21
-16
frontend/src/components/ProfilePage.tsx
+21
-16
frontend/src/components/ProfilePage.tsx
···
1
-
import React, { useState, useEffect, useRef } from 'react';
1
+
import React, { useState, useEffect, useRef, useCallback } from 'react';
2
2
import { useParams } from 'react-router-dom';
3
3
import { ActorProfile, PostResponse } from '../types';
4
4
import { ApiClient } from '../api';
···
67
67
fetchProfile();
68
68
}, [account]);
69
69
70
-
const fetchMorePosts = async (cursor: string) => {
70
+
const fetchMorePosts = useCallback(async (cursorToUse: string) => {
71
71
if (!account || loadingMore || !hasMore) return;
72
72
73
73
try {
74
74
setLoadingMore(true);
75
-
const data = await ApiClient.getProfilePosts(account, cursor);
75
+
const data = await ApiClient.getProfilePosts(account, cursorToUse);
76
76
setPosts(prev => [...prev, ...data.posts]);
77
77
setCursor(data.cursor || null);
78
78
setHasMore(!!(data.cursor && data.posts.length > 0));
···
81
81
} finally {
82
82
setLoadingMore(false);
83
83
}
84
-
};
84
+
}, [account, loadingMore, hasMore]);
85
85
86
86
useEffect(() => {
87
87
const observer = new IntersectionObserver(
88
88
(entries) => {
89
-
if (entries[0].isIntersecting && hasMore && !loadingMore && !loading) {
90
-
if (cursor) {
91
-
fetchMorePosts(cursor);
92
-
}
89
+
if (entries[0].isIntersecting && hasMore && !loadingMore && !loading && cursor) {
90
+
fetchMorePosts(cursor);
93
91
}
94
92
},
95
93
{ threshold: 0.1 }
96
94
);
97
95
98
-
if (observerTarget.current) {
99
-
observer.observe(observerTarget.current);
96
+
const currentTarget = observerTarget.current;
97
+
if (currentTarget) {
98
+
observer.observe(currentTarget);
100
99
}
101
100
102
101
return () => {
103
-
if (observerTarget.current) {
104
-
observer.unobserve(observerTarget.current);
102
+
if (currentTarget) {
103
+
observer.unobserve(currentTarget);
105
104
}
106
105
};
107
-
}, [hasMore, loadingMore, loading, cursor]);
106
+
}, [hasMore, loadingMore, loading, cursor, fetchMorePosts]);
108
107
109
108
if (loading) {
110
109
return (
···
216
215
<p>{activeTab === 'posts' ? 'No posts yet' : 'No replies yet'}</p>
217
216
</div>
218
217
)}
219
-
{hasMore && <div ref={observerTarget} style={{ height: '20px' }} />}
220
-
{loadingMore && (
221
-
<div className="loading-more">Loading more posts...</div>
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>
222
227
)}
223
228
</div>
224
229
</div>