4 Commits

Author SHA1 Message Date
eedb9a24f3 bumping up to 0.0.7
All checks were successful
continuous-integration/drone/tag Build is passing
2025-11-07 21:34:14 +05:30
998c3d490d baking env in build 2025-11-07 21:33:44 +05:30
bb3f733ffc baking env in build 2025-11-07 21:32:31 +05:30
ce7b5dab6b infinity scrolling init 2025-11-07 21:27:29 +05:30
5 changed files with 74 additions and 26 deletions

View File

@@ -63,6 +63,9 @@ steps:
- name: build-image
image: docker:24
environment:
API_BASE_URL:
from_secret: API_BASE_URL
volumes:
- name: dockersock
path: /var/run/docker.sock
@@ -70,7 +73,12 @@ steps:
- IMAGE_TAG=$(cat /drone/src/LATEST_TAG.txt | tr -d '\n')
- echo "🔨 Building Docker image apps/blog:$IMAGE_TAG ..."
- docker build --network=host -t apps/blog:$IMAGE_TAG -t apps/blog:latest /drone/src
- |
docker build --network=host \
--build-arg VITE_API_BASE_URL="$API_BASE_URL" \
-t apps/blog:$IMAGE_TAG \
-t apps/blog:latest \
/drone/src
- name: push-image
image: docker:24
@@ -108,9 +116,6 @@ steps:
- name: run-container
image: docker:24
environment:
API_BASE_URL:
from_secret: API_BASE_URL
volumes:
- name: dockersock
path: /var/run/docker.sock
@@ -123,7 +128,6 @@ steps:
--name blog \
-p 3002:3000 \
-e NODE_ENV=production \
-e VITE_API_BASE_URL="$API_BASE_URL" \
--restart always \
apps/blog:$IMAGE_TAG

View File

@@ -14,7 +14,8 @@ RUN npm ci
COPY . .
# Build the app
RUN npm run build
ARG VITE_API_BASE_URL
RUN VITE_API_BASE_URL=$VITE_API_BASE_URL npm run build
# Stage 2: Static file server (BusyBox)
FROM busybox:latest

View File

@@ -1,6 +1,6 @@
{
"name": "aetoskia-blog-app",
"version": "0.0.4",
"version": "0.0.7",
"private": true,
"scripts": {
"dev": "vite",

View File

@@ -61,7 +61,14 @@ export default function Blog(props: { disableCustomTheme?: boolean }) {
{selectedArticle === null ? (
<>
<MainContent articles={articles} onSelectArticle={handleSelectArticle} />
<Latest articles={articles.slice(0, 3)} /> {/* show 3 most recent */}
<Latest
articles={articles}
onSelectArticle={handleSelectArticle}
onLoadMore={async (offset, limit) => {
// Optional: fetch more from API (if you want true pagination)
// await fetchMoreArticles(offset, limit);
}}
/>
</>
) : (
<Article article={articles[selectedArticle]} onBack={handleBack} />

View File

@@ -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<void>; // optional async callback
}
export default function Latest({ articles, onSelectArticle }: LatestProps) {
const [focusedCardIndex, setFocusedCardIndex] = React.useState<number | null>(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<HTMLDivElement | null>(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 (
<div>
<Box>
<Typography variant="h2" gutterBottom>
Latest
</Typography>
@@ -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}
<NavigateNextRoundedIcon
className="arrow"
sx={{ fontSize: '1rem' }}
/>
<NavigateNextRoundedIcon className="arrow" sx={{ fontSize: '1rem' }} />
</TitleTypography>
<StyledTypography variant="body2" color="text.secondary" gutterBottom>
{article.description}
</StyledTypography>
@@ -148,9 +182,11 @@ export default function Latest({ articles, onSelectArticle }: LatestProps) {
</Grid>
))}
</Grid>
<Box sx={{ display: 'flex', flexDirection: 'row', pt: 4 }}>
<Pagination hidePrevButton hideNextButton count={10} boundaryCount={10} />
{/* Infinite scroll loader */}
<Box ref={loaderRef} sx={{ display: 'flex', justifyContent: 'center', py: 3 }}>
{loadingMore && <CircularProgress size={28} />}
</Box>
</Box>
</div>
);
}