Feed and user Feed done

This commit is contained in:
MisbehavedNinjaRadiator 2025-06-30 18:50:04 +02:00
parent 9cf6531f02
commit 04f4198ebf
29 changed files with 331 additions and 10621 deletions

View file

@ -1,6 +1,6 @@
import React, { useEffect, useRef, useState } from "react";
import "./notFound.css";
import ButtonPrimary from "../../components/ButtonRotkehlchen";
import ButtonPrimary from "../../components/buttons/buttonRotkehlchen/ButtonRotkehlchen";
type Block = {
x: number;

View file

@ -1,7 +1,7 @@
import "./loginAndSignUpPage.css";
import { useEffect, useState } from "react";
import api from "../api/axios";
import ButtonRotkehlchen from "../components/ButtonRotkehlchen";
import ButtonRotkehlchen from "../components/buttons/buttonRotkehlchen/ButtonRotkehlchen";
import { useLocation, useNavigate } from "react-router-dom";
import { useAuth } from "../api/Auth";
import { createTheme, useMediaQuery } from "@mui/material";

View file

@ -5,7 +5,7 @@ import { useNavigate } from "react-router-dom";
import Chip from '@mui/material/Chip';
import Autocomplete from '@mui/material/Autocomplete';
import TextField from '@mui/material/TextField';
import ButtonPrimary from "../components/ButtonRotkehlchen";
import ButtonPrimary from "../components/buttons/buttonRotkehlchen/ButtonRotkehlchen";
import api from "../api/axios";
import { useAuth } from "../api/Auth";

View file

@ -3,7 +3,7 @@ import QuiltedImageList from "../components/profile/QuiltedImageList";
import { StyledEngineProvider, Divider } from "@mui/material";
import ChangeAvatarDialog from "../components/profile/ChangeAvatarDialog";
import Bio from "../components/profile/Bio";
import RotkehlchenButton from "../components/ButtonRotkehlchen";
import RotkehlchenButton from "../components/buttons/buttonRotkehlchen/ButtonRotkehlchen";
import api, { redirectToLogin } from "../api/axios";
import { useAuth } from "../api/Auth";
import { useNavigate, useParams } from "react-router-dom";
@ -28,11 +28,11 @@ function Profile() {
const userProfile = async () => {
try {
const response = await api.get(`/profile/${username}`);
const response = await api.get<{ data: UserProfile }>(`/profile/${username}`);
setUserData(response.data.data);
return;
} catch (error) {
navigate("/"); /* replace to 404 page */
navigate("/notfound");
console.error("Error fetching user profile:", error);
return null;
}

View file

@ -0,0 +1,136 @@
import React, { useState, useEffect, useRef } from "react";
import Post from "../../components/post/Post";
import "./feed.css";
import api from "../../api/axios";
import WelcomeMessage from "../../components/welcomeMessage/welcomeMessage";
import { useAuth } from "../../api/Auth";
import LogInButton from "../../components/buttons/LogInButton";
import SignUpButton from "../../components/buttons/SignUpButton";
import NaggingFooter from "../../components/naggingFooter/NaggingFooter";
import { useLocation, useParams } from "react-router-dom";
interface PostListItem {
id: string;
createdAt: string;
description: string;
}
interface FeedProps {
username?: string;
}
function Feed({ username }: FeedProps) {
const [posts, setPosts] = useState<PostListItem[]>([]);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
const [nextCursor, setNextCursor] = useState<string | null>(null);
const feedRef = useRef<HTMLDivElement | null>(null);
const PAGE_SIZE = 10;
const { user } = useAuth();
const scrollTargetRef = useRef<string | null>(null);
const location = useLocation();
useEffect(() => {
// Remove the # character from the hash
const hashValue = location.hash.replace("#", "");
console.log(hashValue);
console.log("Hash value:", hashValue);
if (hashValue) {
scrollTargetRef.current = hashValue;
}
}, [location]);
const fetchPosts = async () => {
if (loading || !hasMore) return;
setLoading(true);
try {
let url: string;
if (username) {
url = `/posts/getUserPosts/${encodeURIComponent(username)}`;
const response = await api.get<{ posts: PostListItem[] }>(url);
setPosts(response.data.posts);
setHasMore(false);
} else {
url = `/feed?limit=${PAGE_SIZE}`;
if (nextCursor) {
url = `/feed?createdAt=${encodeURIComponent(
nextCursor
)}&limit=${PAGE_SIZE}`;
}
interface FeedResponse {
posts: PostListItem[];
nextCursor: string | null;
}
const response = await api.get<FeedResponse>(url);
const { posts: newPosts, nextCursor: newCursor } = response.data;
const tempPost: PostListItem[] = [...posts, ...newPosts];
setPosts(tempPost);
setNextCursor(newCursor);
setHasMore(!!newCursor && newPosts.length > 0);
}
} catch (error) {
console.error("Error fetching posts:", error);
} finally {
setLoading(false);
}
};
useEffect(() => {
if (username) return;
const onScroll = () => {
if (loading || !hasMore) return;
if (
window.innerHeight + window.scrollY >=
document.body.offsetHeight - 100
) {
fetchPosts();
}
};
window.addEventListener("scroll", onScroll);
return () => {
window.removeEventListener("scroll", onScroll);
};
}, [loading, hasMore, nextCursor, username]);
useEffect(() => {
setPosts([]);
setNextCursor(null);
setHasMore(true);
fetchPosts();
}, [username]);
return (
<div className={user ? "loggedInfeedContainer" : "feedContainer"}>
{!user && (
<div className="welcome-for-logged-out">
<WelcomeMessage />
<SignUpButton />
<LogInButton />
</div>
)}
<main className="feedContent" ref={feedRef}>
{posts.length === 0 && !loading && <div>Keine Posts gefunden.</div>}
{posts.map((post) => (
<div id={post.id} key={post.id} className="feed-post-container">
<Post
postId={post.id}
autoScroll={post.id === scrollTargetRef.current}
/>
</div>
))}
{loading && <div className="loading">Loading more posts...</div>}
{!hasMore && <div className="no-more-posts-message">No more posts</div>}
</main>
{!user && <NaggingFooter />}
</div>
);
}
export function UserFeedRoute() {
const { user } = useParams<{ user: string }>();
return <Feed username={user} />;
}
export default Feed;

View file

@ -0,0 +1,59 @@
.feedContainer {
min-height: 100vh;
}
.feedContent {
flex: 1;
overflow-y: auto;
display: flex;
flex-wrap: nowrap;
flex-direction: column;
justify-content: center;
width: 100%;
max-width: 800px;
margin: 0 auto;
padding: 1rem 0;
}
.loading {
width: 100%;
text-align: center;
margin-top: 1rem;
font-weight: bold;
color: #333;
}
.feed-post-container {
width: 100%;
max-width: 600px;
margin: 0.1rem auto;
box-sizing: border-box;
display: flex;
justify-content: center;
}
.no-more-posts-message {
color: white;
}
/* Desktop view*/
@media (min-width: 768px) {
.feedContainer {
display: grid;
grid-template-columns: 40% 60%;
}
.welcome-for-logged-out {
position: sticky;
top: 5rem;
left: 5rem;
right: 1rem;
align-self: center;
align-self: flex-start;
}
.feedContent {
width: 100%;
max-width: 800px;
margin: 0 auto;
padding: 0 2rem 0;
}
}

View file

@ -99,7 +99,7 @@
justify-content: start;
width: 50vw;
height: 60vh;
min-height: 400px;
min-height: 60vh;
min-width: 500px;
}