diff --git a/frontend/src/Features/CreatePost/CreatePost.tsx b/frontend/src/Features/CreatePost/CreatePost.tsx index 93b098c..a53be59 100644 --- a/frontend/src/Features/CreatePost/CreatePost.tsx +++ b/frontend/src/Features/CreatePost/CreatePost.tsx @@ -24,7 +24,7 @@ export const CreatePost = () => { return; } - api.post('/api/posts/create', null, {params: {content: contentToSave}}).then((response) => { + api.post('/api/posts', null, {params: {content: contentToSave}}).then((response) => { if (response.status === 200) { close(); } diff --git a/frontend/src/Features/MainPage/MainPage.tsx b/frontend/src/Features/MainPage/MainPage.tsx index 01acfa9..d6b7858 100644 --- a/frontend/src/Features/MainPage/MainPage.tsx +++ b/frontend/src/Features/MainPage/MainPage.tsx @@ -1,34 +1,64 @@ -import {Box, Title} 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"; -import {PostDTO} from "./types/Post.tsx"; export const MainPage = () => { + const [posts, setPosts] = useState([]); + const [page, setPage] = useState(0); + const [hasMore, setHasMore] = useState(true); + const [loading, setLoading] = useState(false); + + const loadMorePosts = async () => { + if (loading || !hasMore) return; - const [posts, setPosts] = useState([]); + 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(() => { - api.get("/api/posts/get-all").then((response) => { - setPosts(response.data); - }); + // Initial load + loadMorePosts(); }, []); - return ( - - Main page - - {/*Posts*/} - {posts.map((post) => ( - { - return "https://picsum.photos/seed/" + Math.random() + "/800/2200"; - }) - } - /> - ))} - - ) -} \ No newline at end of file + return ( + + + + {posts.map((post) => ( + { + return "https://picsum.photos/seed/" + Math.random() + "/800/2200"; + }) + } + /> + ))} + + + + ); +}; \ No newline at end of file diff --git a/frontend/src/Features/MainPage/components/InfiniteScroll/InfiniteScroll.tsx b/frontend/src/Features/MainPage/components/InfiniteScroll/InfiniteScroll.tsx new file mode 100644 index 0000000..b5c00ca --- /dev/null +++ b/frontend/src/Features/MainPage/components/InfiniteScroll/InfiniteScroll.tsx @@ -0,0 +1,65 @@ +import React, {useEffect, useRef} from "react"; +import {Box, Loader, Text} from "@mantine/core"; + +interface InfiniteScrollProps { + loadMore: () => Promise; + hasMore: boolean; + loading: boolean; + children: React.ReactNode; +} + +export const InfiniteScroll: React.FC = ({ + loadMore, + hasMore, + loading, + children, + }) => { + const sentinelRef = useRef(null); + const observer = useRef(null); + + useEffect(() => { + // IntersectionObserver callback + const handleIntersection: IntersectionObserverCallback = (entries) => { + const [entry] = entries; + if (entry.isIntersecting && hasMore && !loading) { + loadMore(); + } + }; + + 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 + }); + + observer.current.observe(sentinelRef.current); + } + + return () => { + if (observer.current && sentinelRef.current) { + observer.current.unobserve(sentinelRef.current); + } + }; + }, [hasMore, loading, loadMore]); + + return ( + + {children} + {/* Sentinel element */} +
+ {/* Loading indicator */} + {loading && ( + + + + )} + {/* End of data */} + {!hasMore && !loading && ( + + No more posts ಥ_ಥ + + )} + + ); +}; diff --git a/frontend/src/Features/MainPage/components/InfiniteScroll/index.tsx b/frontend/src/Features/MainPage/components/InfiniteScroll/index.tsx new file mode 100644 index 0000000..978a52c --- /dev/null +++ b/frontend/src/Features/MainPage/components/InfiniteScroll/index.tsx @@ -0,0 +1 @@ +export {InfiniteScroll} from './InfiniteScroll'; \ No newline at end of file diff --git a/frontend/src/Features/MainPage/types/Post.tsx b/frontend/src/Features/MainPage/types/Post.tsx index ff2fa61..fbf1870 100644 --- a/frontend/src/Features/MainPage/types/Post.tsx +++ b/frontend/src/Features/MainPage/types/Post.tsx @@ -1,5 +1,14 @@ export type PostDTO = { - content: string; - ownerLogin: string; - createdAt: string; -} \ No newline at end of file + id: string, + content: string, + createdAt: string, + numberOfComments: number, + author: { + id: string; + name: string; + surname: string; + login: string; + profilePicture: string | null; + }, + photosUrls?: string[] +}; diff --git a/frontend/src/Features/Root/components/Navbar/Navbar.tsx b/frontend/src/Features/Root/components/Navbar/Navbar.tsx index 7b764c3..a494f10 100644 --- a/frontend/src/Features/Root/components/Navbar/Navbar.tsx +++ b/frontend/src/Features/Root/components/Navbar/Navbar.tsx @@ -31,7 +31,7 @@ export const Navbar = () => { useEffect(() => { if (auth.user) { - api.get('/api/users/get-basic-user-info', {params: {login: auth.user.login}}).then((response) => { + api.get('/api/users/basic-user-info', {params: {login: auth.user.login}}).then((response) => { setBasicUserInfo(response.data); }); } diff --git a/frontend/src/Features/shared/components/Cards/Post/Post.tsx b/frontend/src/Features/shared/components/Cards/Post/Post.tsx index 4f387f9..f6648c1 100644 --- a/frontend/src/Features/shared/components/Cards/Post/Post.tsx +++ b/frontend/src/Features/shared/components/Cards/Post/Post.tsx @@ -1,5 +1,16 @@ -import {Avatar, BackgroundImage, Box, Button, Card, Divider, Group, SimpleGrid, Stack, Text} from "@mantine/core"; -import {User} from "../../../types/User.tsx"; +import { + Avatar, + BackgroundImage, + Badge, + Box, + Button, + Card, + Divider, + Group, + SimpleGrid, + Stack, + Text +} from "@mantine/core"; import {InnerHtmlHandler} from "../../InnerHtmlHandler"; import {DateFormatter} from "../../../utils/DateFormatter.tsx"; import {IconMessage, IconPaw, IconShare3} from "@tabler/icons-react"; @@ -7,18 +18,12 @@ import {useAuthStore} from "../../../services/authStore.ts"; import {useNavigate} from "react-router-dom"; import {ImageWithSkeleton} from "../../ImageWithSkeleton"; import {MenuPost} from "./components/MenuPost"; +import {PostDTO} from "../../../../MainPage/types/Post.tsx"; -type PostProps = { - ownerLogin: string; - contentHtml: string; - photosUrls?: string[]; - createdAt: string; -} - -export const Post = (props: PostProps) => { +export const Post = (props: PostDTO) => { const auth = useAuthStore(); - const isOwner = auth.user?.login === props.ownerLogin; + const isOwner = auth.user?.login === props.author.login; const navigate = useNavigate(); /*Render this element each time when number of photos will change*/ @@ -92,7 +97,6 @@ export const Post = (props: PostProps) => { > { ); }; - const user = { - name: "John", - surname: "Doe", - login: "johndoe", - } as User; - return ( - + - navigate(`/profile/${auth.user?.tag}`)} style={{cursor: "pointer"}}> + navigate(`/profile/@${props.author.login}`)} style={{cursor: "pointer"}}> - {user.name} {user.surname} + {props.author.name} {props.author.surname} {DateFormatter(props.createdAt)} {isOwner && - + } - + {/*// Show photos if they exist*/} {props.photosUrls && props.photosUrls.length > 0 && ( @@ -155,7 +153,12 @@ export const Post = (props: PostProps) => { -