Files
frontend-gotosocial/src/pages/LocalTimelinePage.js
2025-09-29 02:07:21 +02:00

264 líneas
6.1 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import Post from '../components/Post/Post';
import api from '../services/api';
import { FaSpinner, FaExclamationTriangle, FaUsers } from 'react-icons/fa';
const PageContainer = styled.div`
max-width: 100%;
`;
const PageHeader = styled.div`
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 2rem;
`;
const PageTitle = styled.h1`
color: #1f2937;
margin: 0;
font-size: 1.8rem;
display: flex;
align-items: center;
gap: 0.5rem;
`;
const PageDescription = styled.p`
color: #6b7280;
margin: 0.5rem 0 0 0;
line-height: 1.5;
`;
const LoadingContainer = styled.div`
text-align: center;
padding: 2rem;
color: #6b7280;
`;
const ErrorContainer = styled.div`
background: #fef2f2;
border: 1px solid #fecaca;
border-radius: 8px;
padding: 1rem;
margin: 1rem 0;
color: #dc2626;
display: flex;
align-items: center;
gap: 0.5rem;
`;
const LoadMoreButton = styled.button`
width: 100%;
padding: 1rem;
background: #f9fafb;
border: 1px solid #e5e7eb;
border-radius: 8px;
color: #6b7280;
cursor: pointer;
margin: 1rem 0;
&:hover {
background: #f3f4f6;
}
&:disabled {
cursor: not-allowed;
opacity: 0.5;
}
`;
const EmptyState = styled.div`
text-align: center;
padding: 3rem;
color: #6b7280;
.icon {
font-size: 4rem;
margin-bottom: 1rem;
color: #d1d5db;
}
h3 {
margin: 0 0 0.5rem 0;
color: #374151;
}
p {
margin: 0;
line-height: 1.5;
}
`;
const LocalTimelinePage = () => {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [loadingMore, setLoadingMore] = useState(false);
const [hasMore, setHasMore] = useState(true);
useEffect(() => {
loadTimeline();
}, []);
const loadTimeline = async (maxId = null) => {
try {
if (!maxId) {
setLoading(true);
setError(null);
}
const timelineData = await api.getPublicTimeline({
max_id: maxId,
limit: 20,
local: true // Only local content
});
if (maxId) {
setPosts(prev => [...prev, ...timelineData]);
setLoadingMore(false);
} else {
setPosts(timelineData);
setLoading(false);
}
setHasMore(timelineData.length === 20);
} catch (err) {
console.error('Error loading local timeline:', err);
if (err.response?.status === 401) {
setError('Authentication required to view the local timeline.');
} else if (err.response?.status === 403) {
setError('Local timeline is not available on this instance.');
} else if (err.code === 'ECONNABORTED') {
setError('Request timeout. Please check your connection and try again.');
} else {
setError('Failed to load local timeline. Please try again.');
}
setLoading(false);
setLoadingMore(false);
}
};
const handleFavorite = async (post) => {
try {
let updatedPost;
if (post.favourited) {
updatedPost = await api.unfavoriteStatus(post.id);
} else {
updatedPost = await api.favoriteStatus(post.id);
}
setPosts(prev => prev.map(p => p.id === post.id ? updatedPost : p));
} catch (err) {
console.error('Error toggling favorite:', err);
}
};
const handleReblog = async (post) => {
try {
let updatedPost;
if (post.reblogged) {
updatedPost = await api.unreblogStatus(post.id);
} else {
updatedPost = await api.reblogStatus(post.id);
}
setPosts(prev => prev.map(p => p.id === post.id ? updatedPost : p));
} catch (err) {
console.error('Error toggling reblog:', err);
}
};
const handleLoadMore = () => {
if (posts.length > 0 && hasMore && !loadingMore) {
setLoadingMore(true);
const lastPost = posts[posts.length - 1];
loadTimeline(lastPost.id);
}
};
if (loading) {
return (
<PageContainer>
<PageHeader>
<PageTitle>
<FaUsers />
Local Timeline
</PageTitle>
</PageHeader>
<LoadingContainer>
<FaSpinner style={{ animation: 'spin 1s linear infinite', fontSize: '2rem', marginBottom: '1rem' }} />
<div>Loading local timeline...</div>
</LoadingContainer>
</PageContainer>
);
}
return (
<PageContainer>
<PageHeader>
<PageTitle>
<FaUsers />
Local Timeline
</PageTitle>
</PageHeader>
<PageDescription>
Public posts from users on this instance only.
This is a great way to see what your local community is talking about.
</PageDescription>
{error && (
<ErrorContainer>
<FaExclamationTriangle />
{error}
</ErrorContainer>
)}
{posts.length === 0 && !loading ? (
<EmptyState>
<div className="icon">
<FaUsers />
</div>
<h3>No local posts</h3>
<p>
There are no local public posts available right now.<br />
This instance might be new or have limited local activity.
</p>
</EmptyState>
) : (
<>
{posts.map(post => (
<Post
key={post.id}
post={post}
onFavorite={handleFavorite}
onReblog={handleReblog}
onReply={(post) => console.log('Reply to:', post)}
/>
))}
{hasMore && (
<LoadMoreButton
onClick={handleLoadMore}
disabled={loadingMore}
>
{loadingMore ? (
<>
<FaSpinner style={{ animation: 'spin 1s linear infinite', marginRight: '0.5rem' }} />
Loading more posts...
</>
) : (
'Load more posts'
)}
</LoadMoreButton>
)}
</>
)}
</PageContainer>
);
};
export default LocalTimelinePage;