diff --git a/src/blog/Blog.tsx b/src/blog/Blog.tsx
index 087b2a1..12600ec 100644
--- a/src/blog/Blog.tsx
+++ b/src/blog/Blog.tsx
@@ -61,7 +61,14 @@ export default function Blog(props: { disableCustomTheme?: boolean }) {
{selectedArticle === null ? (
<>
- {/* show 3 most recent */}
+ {
+ // Optional: fetch more from API (if you want true pagination)
+ // await fetchMoreArticles(offset, limit);
+ }}
+ />
>
) : (
diff --git a/src/blog/components/Latest.tsx b/src/blog/components/Latest.tsx
index 664ca6e..c4f808e 100644
--- a/src/blog/components/Latest.tsx
+++ b/src/blog/components/Latest.tsx
@@ -3,10 +3,10 @@ import Avatar from '@mui/material/Avatar';
import AvatarGroup from '@mui/material/AvatarGroup';
import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid';
-import Pagination from '@mui/material/Pagination';
import Typography from '@mui/material/Typography';
import { styled } from '@mui/material/styles';
import NavigateNextRoundedIcon from '@mui/icons-material/NavigateNextRounded';
+import CircularProgress from '@mui/material/CircularProgress';
import type { Article } from '../providers/Article'; // โ
import type for correctness
@@ -92,19 +92,58 @@ function Author({ authors }: { authors: { name: string; avatar: string }[] }) {
interface LatestProps {
articles: Article[];
onSelectArticle?: (index: number) => void;
+ onLoadMore?: (offset: number, limit: number) => Promise; // optional async callback
}
-export default function Latest({ articles, onSelectArticle }: LatestProps) {
- const [focusedCardIndex, setFocusedCardIndex] = React.useState(null);
+export default function Latest({ articles, onSelectArticle, onLoadMore }: LatestProps) {
+ const [visibleCount, setVisibleCount] = React.useState(2);
+ const [loadingMore, setLoadingMore] = React.useState(false);
- const handleFocus = (index: number) => setFocusedCardIndex(index);
- const handleBlur = () => setFocusedCardIndex(null);
+ const displayedArticles = articles.slice(0, visibleCount);
- // limit to 4-6 items for visual balance
- const displayedArticles = articles.slice(0, 6);
+ // Intersection Observer ref
+ const loaderRef = React.useRef(null);
+
+ React.useEffect(() => {
+ if (!loaderRef.current) return;
+
+ const observer = new IntersectionObserver(
+ async (entries) => {
+ const first = entries[0];
+ if (first.isIntersecting && !loadingMore && visibleCount < articles.length) {
+ console.log('๐ก Intersection triggered โ loading more blogs...');
+ setLoadingMore(true);
+
+ if (onLoadMore) {
+ console.log(`๐ก Calling onLoadMore(offset=${visibleCount}, limit=2)`);
+ await onLoadMore(visibleCount, 2);
+ }
+
+ setVisibleCount((prev) => {
+ const newCount = prev + 2;
+ console.log(`โ
Increasing visibleCount from ${prev} โ ${newCount}`);
+ return newCount;
+ });
+
+ setLoadingMore(false);
+ }
+ },
+ { threshold: 0.5 }
+ );
+
+ const current = loaderRef.current;
+ observer.observe(current);
+
+ console.log('๐ IntersectionObserver attached to loaderRef:', loaderRef.current);
+
+ return () => {
+ if (current) observer.unobserve(current);
+ console.log('๐งน IntersectionObserver detached');
+ };
+ }, [loadingMore, visibleCount, articles.length, onLoadMore]);
return (
-
+
Latest
@@ -128,17 +167,12 @@ export default function Latest({ articles, onSelectArticle }: LatestProps) {
gutterBottom
variant="h6"
tabIndex={0}
- onFocus={() => handleFocus(index)}
- onBlur={handleBlur}
onClick={() => onSelectArticle?.(index)}
- className={focusedCardIndex === index ? 'Mui-focused' : ''}
>
{article.title}
-
+
+
{article.description}
@@ -148,9 +182,11 @@ export default function Latest({ articles, onSelectArticle }: LatestProps) {
))}
-
-
+
+ {/* Infinite scroll loader */}
+
+ {loadingMore && }
-
+
);
}