Skip to content

Commit

Permalink
feature: Connect posts fully with backend
Browse files Browse the repository at this point in the history
Finish creating InfiniteScroll
- Remove Strict mode
- Correct some small bugs on Posts and ImageWithSkeleton

Refs: 869768m7p
Signed-off-by: Jimmy <[email protected]>
  • Loading branch information
JimTheCat committed Dec 22, 2024
1 parent f4f6275 commit 77f4cf9
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 91 deletions.
65 changes: 58 additions & 7 deletions frontend/src/Features/MainPage/MainPage.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,64 @@
import {Box, Center} from "@mantine/core";
import {useEffect, useState} from "react";
import {PostDTO} from "./types/Post.tsx";
import api from "../shared/services/api.ts";
import {Box, Stack} from "@mantine/core";
import {InfiniteScroll} from "./components/InfiniteScroll";
import {Post} from "../shared/components/Cards/Post";

export const MainPage = () => {
const [posts, setPosts] = useState<PostDTO[]>([]);
const [page, setPage] = useState(0);
const [hasMore, setHasMore] = useState(true);
const [loading, setLoading] = useState(false);

const loadMorePosts = async () => {
if (loading || !hasMore) return;

setLoading(true);
try {
const response = await api.get(`/api/posts`, {
params: {pageNo: page, pageSize: 3},
});

const data = response.data;

setPosts((prevPosts) => [...prevPosts, ...data.content]);
setHasMore(data.totalPages > page);
setPage((prevPage) => prevPage + 1);
} catch (error) {
console.error("Error loading posts:", error);
} finally {
setLoading(false);
}
};

useEffect(() => {
// Initial load
loadMorePosts();
}, []);

return (
<Box p={"md"}>
<Center>
{/* Post rendering */}
<InfiniteScroll/>
</Center>
<Box p="lg">
<InfiniteScroll loadMore={loadMorePosts} hasMore={hasMore} loading={loading}>
<Stack align={"center"} gap="lg">
{posts.map((post) => (
<Post
key={post.id}
id={post.id}
author={post.author}
content={post.content}
createdAt={post.createdAt}
numberOfComments={post.numberOfComments}
photosUrls={
// generate 100 random photos
Array.from({length: 100}, () => {
return "https://picsum.photos/seed/" + Math.random() + "/800/2200";
})
}
/>
))}
</Stack>
</InfiniteScroll>
</Box>
);
};
};
Original file line number Diff line number Diff line change
@@ -1,80 +1,65 @@
import {useCallback, useEffect, useState} from "react";
import api from "../../../shared/services/api.ts";
import {Group, Loader, ScrollArea, Text} from "@mantine/core";
import {Post} from "../../../shared/components/Cards/Post";
import {PostDTO} from "../../types/Post.tsx";
import React, {useEffect, useRef} from "react";
import {Box, Loader, Text} from "@mantine/core";

export const InfiniteScroll = () => {
const [posts, setPosts] = useState<PostDTO[]>([]);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
const [page, setPage] = useState(0);
interface InfiniteScrollProps {
loadMore: () => Promise<void>;
hasMore: boolean;
loading: boolean;
children: React.ReactNode;
}

const loadPosts = useCallback(async () => {
if (loading || !hasMore) return;
export const InfiniteScroll: React.FC<InfiniteScrollProps> = ({
loadMore,
hasMore,
loading,
children,
}) => {
const sentinelRef = useRef<HTMLDivElement | null>(null);
const observer = useRef<IntersectionObserver | null>(null);

setLoading(true);
useEffect(() => {
// IntersectionObserver callback
const handleIntersection: IntersectionObserverCallback = (entries) => {
const [entry] = entries;
if (entry.isIntersecting && hasMore && !loading) {
loadMore();
}
};

try {
const response = await api.get(`/api/posts`, {
params: {pageNo: page, pageSize: 10},
if (sentinelRef.current) {
observer.current = new IntersectionObserver(handleIntersection, {
root: null,
rootMargin: "100px", // Load earlier when close to the bottom
threshold: 0.1, // Trigger when sentinel is at least 10% visible
});

const newPosts = response.data.content;

setPosts((prevPosts) => [...prevPosts, ...newPosts]);
setHasMore(response.data.totalPages > page + 1);
setPage((prevPage) => prevPage + 1);
} catch (error) {
console.error('Error loading posts:', error);
} finally {
setLoading(false);
observer.current.observe(sentinelRef.current);
}
}, [loading, hasMore, page]);

useEffect(() => {
loadPosts();
}, [loadPosts]);

// Funkcja do obsługi scrolla
const handleScroll = (event: { currentTarget: any; }) => {
const target = event.currentTarget;
if (target.scrollHeight - target.scrollTop <= target.clientHeight + 10) {
loadPosts();
}
};
return () => {
if (observer.current && sentinelRef.current) {
observer.current.unobserve(sentinelRef.current);
}
};
}, [hasMore, loading, loadMore]);

return (
<ScrollArea
onScroll={handleScroll}
offsetScrollbars
>
{posts.map((post) => (
<Post
key={post.id}
author={post.author}
content={post.content}
createdAt={post.createdAt}
numberOfComments={post.numberOfComments}
id={post.id}
photosUrls={
// generate 100 random photos
Array.from({length: 100}, () => {
return "https://picsum.photos/seed/" + Math.random() + "/800/2200";
})
}
/>
))}
<Box style={{position: "relative"}}>
{children}
{/* Sentinel element */}
<div ref={sentinelRef} style={{height: 1, visibility: "hidden"}}/>
{/* Loading indicator */}
{loading && (
<Group justify={"center"} mt="lg">
<Loader/>
</Group>
<Box style={{textAlign: "center", margin: "20px 0"}}>
<Loader size="md"/>
</Box>
)}
{/* End of data */}
{!hasMore && !loading && (
<Text ta="center" c="dimmed" mt="lg">
No more posts to load
No more posts ಥ_ಥ
</Text>
)}
</ScrollArea>
</Box>
);
}
};
3 changes: 1 addition & 2 deletions frontend/src/Features/shared/components/Cards/Post/Post.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ export const Post = (props: PostDTO) => {
>
<Text
size="xl"
w={700}
c="white"
ta="center"
style={{
Expand All @@ -124,7 +123,7 @@ export const Post = (props: PostDTO) => {
};

return (
<Card w={"30vw"} radius={"md"} p={"lg"} my={'lg'}>
<Card w={"30vw"} radius={"md"} p={"lg"}>
<Stack>
<Group justify="space-between">
<Group onClick={() => navigate(`/profile/@${props.author.login}`)} style={{cursor: "pointer"}}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ export const ImageWithSkeleton = (props: {
radius: number | MantineRadius,
style?: MantineStyleProp
}) => {

const [isImageLoaded, setIsImageLoaded] = useState(false);

return (
<>
{!isImageLoaded && <Skeleton height={400} radius={props.radius}/>}
{!isImageLoaded && <Skeleton style={props.style} radius={props.radius}/>}
<Image
src={props.src}
alt={props.alt}
Expand Down
31 changes: 14 additions & 17 deletions frontend/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {StrictMode} from 'react'
import {createRoot} from 'react-dom/client'
import App from './App.tsx'
import './index.css'
Expand Down Expand Up @@ -26,20 +25,18 @@ const theme = createTheme({
});

createRoot(document.getElementById('root')!).render(
<StrictMode>
<MantineProvider theme={theme} defaultColorScheme={'dark'}>
<ModalsProvider>
<AlertProvider>
<ThemeProvider>
<BrowserRouter>
<I18nextProvider i18n={i18next}>
<CookiesPopup/>
<App/>
</I18nextProvider>
</BrowserRouter>
</ThemeProvider>
</AlertProvider>
</ModalsProvider>
</MantineProvider>
</StrictMode>,
<MantineProvider theme={theme} defaultColorScheme={'dark'}>
<ModalsProvider>
<AlertProvider>
<ThemeProvider>
<BrowserRouter>
<I18nextProvider i18n={i18next}>
<CookiesPopup/>
<App/>
</I18nextProvider>
</BrowserRouter>
</ThemeProvider>
</AlertProvider>
</ModalsProvider>
</MantineProvider>
)

0 comments on commit 77f4cf9

Please sign in to comment.